summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES42
-rw-r--r--UPGRADE-15.txt85
-rw-r--r--UPGRADE.txt46
-rw-r--r--apps/app_confbridge.c7
-rw-r--r--apps/app_meetme.c191
-rw-r--r--apps/app_playback.c2
-rw-r--r--apps/app_queue.c457
-rw-r--r--apps/app_stream_echo.c15
-rw-r--r--apps/app_voicemail.c940
-rw-r--r--apps/confbridge/conf_config_parser.c13
-rw-r--r--apps/confbridge/include/confbridge.h1
-rw-r--r--bridges/bridge_native_rtp.c647
-rw-r--r--bridges/bridge_softmix.c48
-rw-r--r--bridges/bridge_softmix/include/bridge_softmix_internal.h2
-rw-r--r--channels/chan_dahdi.c233
-rw-r--r--channels/chan_iax2.c336
-rw-r--r--channels/chan_pjsip.c564
-rw-r--r--channels/chan_sip.c250
-rw-r--r--channels/pjsip/cli_commands.c37
-rw-r--r--channels/pjsip/dialplan_functions.c185
-rw-r--r--channels/pjsip/include/chan_pjsip.h13
-rw-r--r--configs/basic-pbx/modules.conf1
-rw-r--r--configs/samples/cdr.conf.sample2
-rw-r--r--configs/samples/confbridge.conf.sample6
-rw-r--r--configs/samples/config_test.conf.sample8
-rw-r--r--configs/samples/musiconhold.conf.sample23
-rw-r--r--configs/samples/pjsip.conf.sample28
-rw-r--r--configs/samples/voicemail.conf.sample3
-rwxr-xr-xconfigure27
-rw-r--r--configure.ac2
-rw-r--r--contrib/ast-db-manage/config/versions/164abbd708c_add_auto_info_to_endpoint_dtmf_mode.py58
-rw-r--r--contrib/ast-db-manage/config/versions/39959b9c2566_pjsip_stream_maximum.py24
-rw-r--r--contrib/ast-db-manage/config/versions/d7983954dd96_add_ps_endpoints_notify_early_inuse_.py30
-rwxr-xr-xcontrib/scripts/install_prereq2
-rw-r--r--include/asterisk/_private.h1
-rw-r--r--include/asterisk/bridge.h9
-rw-r--r--include/asterisk/channel.h46
-rw-r--r--include/asterisk/config.h11
-rw-r--r--include/asterisk/config_options.h24
-rw-r--r--include/asterisk/core_local.h37
-rw-r--r--include/asterisk/data.h828
-rw-r--r--include/asterisk/format.h4
-rw-r--r--include/asterisk/format_cache.h5
-rw-r--r--include/asterisk/frame.h2
-rw-r--r--include/asterisk/indications.h9
-rw-r--r--include/asterisk/manager.h2
-rw-r--r--include/asterisk/res_pjsip.h32
-rw-r--r--include/asterisk/res_pjsip_presence_xml.h3
-rw-r--r--include/asterisk/res_pjsip_session.h232
-rw-r--r--include/asterisk/rtp_engine.h61
-rw-r--r--include/asterisk/sdp.h12
-rw-r--r--include/asterisk/sdp_options.h249
-rw-r--r--include/asterisk/sdp_state.h127
-rw-r--r--include/asterisk/stream.h27
-rw-r--r--main/app.c13
-rw-r--r--main/asterisk.c5
-rw-r--r--main/bridge.c16
-rw-r--r--main/bridge_channel.c30
-rw-r--r--main/cdr.c1
-rw-r--r--main/channel.c202
-rw-r--r--main/channel_internal_api.c206
-rw-r--r--main/codec_builtin.c8
-rw-r--r--main/config.c49
-rw-r--r--main/config_options.c36
-rw-r--r--main/core_local.c42
-rw-r--r--main/core_unreal.c13
-rw-r--r--main/data.c3346
-rw-r--r--main/format_cache.c8
-rw-r--r--main/http.c11
-rw-r--r--main/indications.c41
-rw-r--r--main/json.c1
-rw-r--r--main/pbx.c52
-rw-r--r--main/pbx_builtins.c78
-rw-r--r--main/rtp_engine.c72
-rw-r--r--main/say.c52
-rw-r--r--main/sdp.c99
-rw-r--r--main/sdp_options.c125
-rw-r--r--main/sdp_private.h21
-rw-r--r--main/sdp_state.c2609
-rw-r--r--main/stream.c64
-rw-r--r--res/res_agi.c4
-rw-r--r--res/res_corosync.c29
-rw-r--r--res/res_musiconhold.c156
-rw-r--r--res/res_odbc.c70
-rw-r--r--res/res_pjsip.c75
-rw-r--r--res/res_pjsip.exports.in1
-rw-r--r--res/res_pjsip/pjsip_configuration.c80
-rw-r--r--res/res_pjsip/pjsip_distributor.c242
-rw-r--r--res/res_pjsip/presence_xml.c9
-rw-r--r--res/res_pjsip_dialog_info_body_generator.c10
-rw-r--r--res/res_pjsip_mwi.c81
-rw-r--r--res/res_pjsip_pidf_body_generator.c2
-rw-r--r--res/res_pjsip_pidf_eyebeam_body_supplement.c2
-rw-r--r--res/res_pjsip_sdp_rtp.c684
-rw-r--r--res/res_pjsip_session.c1178
-rw-r--r--res/res_pjsip_session.exports.in22
-rw-r--r--res/res_pjsip_t38.c268
-rw-r--r--res/res_pjsip_xpidf_body_generator.c2
-rw-r--r--res/res_rtp_asterisk.c859
-rw-r--r--res/res_sdp_translator_pjmedia.c4
-rw-r--r--res/res_stasis.c20
-rw-r--r--res/res_stasis_device_state.c4
-rw-r--r--res/res_stasis_snoop.c22
-rwxr-xr-xrest-api-templates/make_ari_stubs.py2
-rw-r--r--rest-api/resources.json2
-rw-r--r--sounds/Makefile7
-rw-r--r--sounds/sounds.xml27
-rw-r--r--tests/test_config.c85
-rw-r--r--tests/test_sdp.c1221
-rw-r--r--third-party/configure.m45
-rw-r--r--third-party/pjproject/Makefile.rules5
-rw-r--r--third-party/pjproject/configure.m424
-rw-r--r--third-party/pjproject/patches/0070-Set-PJSIP_INV_SUPPORT_UPDATE-correctly-in-pjsip_inv_.patch29
-rw-r--r--third-party/pjproject/patches/config_site.h2
114 files changed, 9684 insertions, 8798 deletions
diff --git a/CHANGES b/CHANGES
index 9bfa506d3..5daa81618 100644
--- a/CHANGES
+++ b/CHANGES
@@ -18,9 +18,35 @@ app_queue
been defined.
------------------------------------------------------------------------------
+--- Functionality changes from Asterisk 14.6.0 to Asterisk 14.7.0 ------------
+------------------------------------------------------------------------------
+
+res_musiconhold
+------------------
+ * By default, when res_musiconhold reloads or unloads, it sends a HUP signal
+ to custom applications (and all descendants), waits 100ms, then sends a
+ TERM signal, waits 100ms, then finally sends a KILL signal. An application
+ which is interacting with an external device and/or spawns children of its
+ own may not be able to exit cleanly in the default times, expecially if sent
+ a KILL signal, or if it's children are getting signals directly from
+ res_musiconhoild. To allow extra time, the 'kill_escalation_delay'
+ class option can be used to set the number of milliseconds res_musiconhold
+ waits before escalating kill signals, with the default being the current
+ 100ms. To control to whom the signals are sent, the "kill_method"
+ class option can be set to "process_group" (the default, existing behavior),
+ which sends signals to the application and its descendants directly, or
+ "process" which sends signals only to the application itself.
+
+------------------------------------------------------------------------------
--- Functionality changes from Asterisk 14.5.0 to Asterisk 14.6.0 ------------
------------------------------------------------------------------------------
+app_voicemail
+------------------
+ * A new global option "imap_poll_logout" was added to specify whether need to
+ disconnect from the IMAP server after polling of mailboxes.
+ Default: no
+
res_pjsip
------------------
* A new endpoint option "refer_blind_progress" was added to turn off notifying
@@ -30,6 +56,14 @@ res_pjsip
Some SIP phones like Mitel/Aastra or Snom keep the line busy until
receive "200 OK".
+ * A new endpoint option "notify_early_inuse_ringing" was added to control
+ whether to notify dialog-info state 'early' or 'confirmed' on Ringing
+ when already INUSE.
+
+ * The endpoint option 'dtmf_mode' has a new option 'auto_dtmf' added. This
+ mode works similar to 'auto' except uses DTMF INFO as fallback instead of
+ INBAND.
+
res_agi
------------------
* The EAGI() application will now look for a dialplan variable named
@@ -49,6 +83,14 @@ chan_pjsip
from the SDP, unless the remote side sends a different codec and we will
switch to match.
+Build System
+------------------
+ * Added a new PJPROJECT_CONFIGURE_OPTS environment variable which can be used
+ to pass arbitrary options to the bundled pjproject configure.
+
+ * Automatically set the bundled pjproject configure --host and --build
+ options to match those supplied for the asterisk configure.
+
------------------------------------------------------------------------------
--- Functionality changes from Asterisk 14.4.0 to Asterisk 14.5.0 ------------
------------------------------------------------------------------------------
diff --git a/UPGRADE-15.txt b/UPGRADE-15.txt
new file mode 100644
index 000000000..4ebe400a6
--- /dev/null
+++ b/UPGRADE-15.txt
@@ -0,0 +1,85 @@
+===========================================================
+===
+=== Information for upgrading between Asterisk versions
+===
+=== These files document all the changes that MUST be taken
+=== into account when upgrading between the Asterisk
+=== versions listed below. These changes may require that
+=== you modify your configuration files, dialplan or (in
+=== some cases) source code if you have your own Asterisk
+=== modules or patches. These files also include advance
+=== notice of any functionality that has been marked as
+=== 'deprecated' and may be removed in a future release,
+=== along with the suggested replacement functionality.
+===
+=== UPGRADE-1.2.txt -- Upgrade info for 1.0 to 1.2
+=== UPGRADE-1.4.txt -- Upgrade info for 1.2 to 1.4
+=== UPGRADE-1.6.txt -- Upgrade info for 1.4 to 1.6
+=== UPGRADE-1.8.txt -- Upgrade info for 1.6 to 1.8
+=== UPGRADE-10.txt -- Upgrade info for 1.8 to 10
+=== UPGRADE-11.txt -- Upgrade info for 10 to 11
+=== UPGRADE-12.txt -- Upgrade info for 11 to 12
+=== UPGRADE-13.txt -- Upgrade info for 12 to 13
+=== UPGRADE-14.txt -- Upgrade info for 13 to 14
+=== UPGRADE-15.txt -- Upgrade info for 14 to 15
+===========================================================
+
+New in 15.0.0:
+
+Core:
+ - The 'Data Retrieval API' has been removed. This API was not actively
+ maintained, was not added to new modules (such as res_pjsip), and there
+ exist better alternatives to acquire the same information, such as the
+ 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:
+ - Support for embedded modules has been removed. This has not worked in
+ many years. LOADABLE_MODULES menuselect option is also removed as
+ loadable module support is now always enabled.
+
+From 14.3.0 to 14.4.0:
+
+res_rtp_asterisk:
+ - The RTP layer of Asterisk now has support for RFC 5761: "Multiplexing RTP
+ Data and Control Packets on a Single Port." For the PJSIP channel driver,
+ chan_pjsip, you can set "rtcp_mux = yes" on a PJSIP endpoint in pjsip.conf
+ to enable the feature. For chan_sip you can set "rtcp_mux = yes" either
+ globally or on a per-peer basis in sip.conf.
+
+New in 14.0.0
+
+ARI:
+ - The policy for when to send "Dial" events has changed. Previously, "Dial"
+ events were sent on the calling channel's topic. However, starting in Asterisk
+ 14, if there is no calling channel on which to send the event, the event is
+ instead sent on the called channel's topic. Note that for the ARI channels
+ resource's dial operation, this means that the "Dial" events will always be
+ sent on the called channel's topic.
+
+Queue:
+ - When reloading the members of a queue, the members added dynamically (i.e.
+ added via the CLI command "queue add" or the AMI action "QueueAdd") now have
+ their ringinuse value updated to the value of the queue. Previously, the
+ ringinuse value for dynamic members was not updated on reload.
+
+Queue log:
+ - New RINGCANCELED event is logged when the caller hangs up while ringing.
+ The data1 field contains number of miliseconds since start of ringing.
+
+Channel Drivers:
+
+chan_dahdi:
+ - Support for specifying a DAHDI channel using a path under /dev/dahdi
+ ("by name") has been removed. It was never used. Instead you should
+ use kernel-level channel number allocation using span assignments.
+ See the documentation of dahdi-linux and dahdi-tools.
+
diff --git a/UPGRADE.txt b/UPGRADE.txt
index 62bb80182..87eabde2d 100644
--- a/UPGRADE.txt
+++ b/UPGRADE.txt
@@ -21,49 +21,5 @@
=== UPGRADE-12.txt -- Upgrade info for 11 to 12
=== UPGRADE-13.txt -- Upgrade info for 12 to 13
=== UPGRADE-14.txt -- Upgrade info for 13 to 14
+=== UPGRADE-15.txt -- Upgrade info for 14 to 15
===========================================================
-
-From 14.4.0 to 14.5.0:
-
-Core:
- - Support for embedded modules has been removed. This has not worked in
- many years. LOADABLE_MODULES menuselect option is also removed as
- loadable module support is now always enabled.
-
-From 14.3.0 to 14.4.0:
-
-res_rtp_asterisk:
- - The RTP layer of Asterisk now has support for RFC 5761: "Multiplexing RTP
- Data and Control Packets on a Single Port." For the PJSIP channel driver,
- chan_pjsip, you can set "rtcp_mux = yes" on a PJSIP endpoint in pjsip.conf
- to enable the feature. For chan_sip you can set "rtcp_mux = yes" either
- globally or on a per-peer basis in sip.conf.
-
-New in 14.0.0
-
-ARI:
- - The policy for when to send "Dial" events has changed. Previously, "Dial"
- events were sent on the calling channel's topic. However, starting in Asterisk
- 14, if there is no calling channel on which to send the event, the event is
- instead sent on the called channel's topic. Note that for the ARI channels
- resource's dial operation, this means that the "Dial" events will always be
- sent on the called channel's topic.
-
-Queue:
- - When reloading the members of a queue, the members added dynamically (i.e.
- added via the CLI command "queue add" or the AMI action "QueueAdd") now have
- their ringinuse value updated to the value of the queue. Previously, the
- ringinuse value for dynamic members was not updated on reload.
-
-Queue log:
- - New RINGCANCELED event is logged when the caller hangs up while ringing.
- The data1 field contains number of miliseconds since start of ringing.
-
-Channel Drivers:
-
-chan_dahdi:
- - Support for specifying a DAHDI channel using a path under /dev/dahdi
- ("by name") has been removed. It was never used. Instead you should
- use kernel-level channel number allocation using span assignments.
- See the documentation of dahdi-linux and dahdi-tools.
-
diff --git a/apps/app_confbridge.c b/apps/app_confbridge.c
index 0f846b659..b2d612df3 100644
--- a/apps/app_confbridge.c
+++ b/apps/app_confbridge.c
@@ -1485,6 +1485,7 @@ static struct confbridge_conference *join_conference_bridge(const char *conferen
ast_bridge_set_talker_src_video_mode(conference->bridge);
} else if (ast_test_flag(&conference->b_profile, BRIDGE_OPT_VIDEO_SRC_SFU)) {
ast_bridge_set_sfu_video_mode(conference->bridge);
+ ast_bridge_set_video_update_discard(conference->bridge, conference->b_profile.video_update_discard);
}
/* Link it into the conference bridges container */
@@ -2145,6 +2146,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 +2238,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 +2484,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 +2513,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;
diff --git a/apps/app_meetme.c b/apps/app_meetme.c
index 71ca9dc9f..d98c418c3 100644
--- a/apps/app_meetme.c
+++ b/apps/app_meetme.c
@@ -69,7 +69,6 @@
#include "asterisk/dial.h"
#include "asterisk/causes.h"
#include "asterisk/paths.h"
-#include "asterisk/data.h"
#include "asterisk/test.h"
#include "asterisk/stasis.h"
#include "asterisk/stasis_channels.h"
@@ -8005,186 +8004,6 @@ static int load_config(int reload)
return sla_load_config(reload);
}
-#define MEETME_DATA_EXPORT(MEMBER) \
- MEMBER(ast_conference, confno, AST_DATA_STRING) \
- MEMBER(ast_conference, dahdiconf, AST_DATA_INTEGER) \
- MEMBER(ast_conference, users, AST_DATA_INTEGER) \
- MEMBER(ast_conference, markedusers, AST_DATA_INTEGER) \
- MEMBER(ast_conference, maxusers, AST_DATA_INTEGER) \
- MEMBER(ast_conference, isdynamic, AST_DATA_BOOLEAN) \
- MEMBER(ast_conference, locked, AST_DATA_BOOLEAN) \
- MEMBER(ast_conference, recordingfilename, AST_DATA_STRING) \
- MEMBER(ast_conference, recordingformat, AST_DATA_STRING) \
- MEMBER(ast_conference, pin, AST_DATA_PASSWORD) \
- MEMBER(ast_conference, pinadmin, AST_DATA_PASSWORD) \
- MEMBER(ast_conference, start, AST_DATA_TIMESTAMP) \
- MEMBER(ast_conference, endtime, AST_DATA_TIMESTAMP)
-
-AST_DATA_STRUCTURE(ast_conference, MEETME_DATA_EXPORT);
-
-#define MEETME_USER_DATA_EXPORT(MEMBER) \
- MEMBER(ast_conf_user, user_no, AST_DATA_INTEGER) \
- MEMBER(ast_conf_user, talking, AST_DATA_BOOLEAN) \
- MEMBER(ast_conf_user, dahdichannel, AST_DATA_BOOLEAN) \
- MEMBER(ast_conf_user, jointime, AST_DATA_TIMESTAMP) \
- MEMBER(ast_conf_user, kicktime, AST_DATA_TIMESTAMP) \
- MEMBER(ast_conf_user, timelimit, AST_DATA_MILLISECONDS) \
- MEMBER(ast_conf_user, play_warning, AST_DATA_MILLISECONDS) \
- MEMBER(ast_conf_user, warning_freq, AST_DATA_MILLISECONDS)
-
-AST_DATA_STRUCTURE(ast_conf_user, MEETME_USER_DATA_EXPORT);
-
-static int user_add_provider_cb(void *obj, void *arg, int flags)
-{
- struct ast_data *data_meetme_user;
- struct ast_data *data_meetme_user_channel;
- struct ast_data *data_meetme_user_volume;
-
- struct ast_conf_user *user = obj;
- struct ast_data *data_meetme_users = arg;
-
- data_meetme_user = ast_data_add_node(data_meetme_users, "user");
- if (!data_meetme_user) {
- return 0;
- }
- /* user structure */
- ast_data_add_structure(ast_conf_user, data_meetme_user, user);
-
- /* user's channel */
- data_meetme_user_channel = ast_data_add_node(data_meetme_user, "channel");
- if (!data_meetme_user_channel) {
- return 0;
- }
-
- ast_channel_data_add_structure(data_meetme_user_channel, user->chan, 1);
-
- /* volume structure */
- data_meetme_user_volume = ast_data_add_node(data_meetme_user, "listen-volume");
- if (!data_meetme_user_volume) {
- return 0;
- }
- ast_data_add_int(data_meetme_user_volume, "desired", user->listen.desired);
- ast_data_add_int(data_meetme_user_volume, "actual", user->listen.actual);
-
- data_meetme_user_volume = ast_data_add_node(data_meetme_user, "talk-volume");
- if (!data_meetme_user_volume) {
- return 0;
- }
- ast_data_add_int(data_meetme_user_volume, "desired", user->talk.desired);
- ast_data_add_int(data_meetme_user_volume, "actual", user->talk.actual);
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief Implements the meetme data provider.
- */
-static int meetme_data_provider_get(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- struct ast_conference *cnf;
- struct ast_data *data_meetme, *data_meetme_users;
-
- AST_LIST_LOCK(&confs);
- AST_LIST_TRAVERSE(&confs, cnf, list) {
- data_meetme = ast_data_add_node(data_root, "meetme");
- if (!data_meetme) {
- continue;
- }
-
- ast_data_add_structure(ast_conference, data_meetme, cnf);
-
- if (ao2_container_count(cnf->usercontainer)) {
- data_meetme_users = ast_data_add_node(data_meetme, "users");
- if (!data_meetme_users) {
- ast_data_remove_node(data_root, data_meetme);
- continue;
- }
-
- ao2_callback(cnf->usercontainer, OBJ_NODATA, user_add_provider_cb, data_meetme_users);
- }
-
- if (!ast_data_search_match(search, data_meetme)) {
- ast_data_remove_node(data_root, data_meetme);
- }
- }
- AST_LIST_UNLOCK(&confs);
-
- return 0;
-}
-
-static const struct ast_data_handler meetme_data_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = meetme_data_provider_get
-};
-
-static const struct ast_data_entry meetme_data_providers[] = {
- AST_DATA_ENTRY("asterisk/application/meetme/list", &meetme_data_provider),
-};
-
-#ifdef TEST_FRAMEWORK
-AST_TEST_DEFINE(test_meetme_data_provider)
-{
- struct ast_channel *chan;
- struct ast_conference *cnf;
- struct ast_data *node;
- struct ast_data_query query = {
- .path = "/asterisk/application/meetme/list",
- .search = "list/meetme/confno=9898"
- };
-
- switch (cmd) {
- case TEST_INIT:
- info->name = "meetme_get_data_test";
- info->category = "/main/data/app_meetme/list/";
- info->summary = "Meetme data provider unit test";
- info->description =
- "Tests whether the Meetme data provider implementation works as expected.";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
-
- chan = ast_channel_alloc(0, AST_STATE_DOWN, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, "MeetMeTest");
- if (!chan) {
- ast_test_status_update(test, "Channel allocation failed\n");
- return AST_TEST_FAIL;
- }
-
- ast_channel_unlock(chan);
-
- cnf = build_conf("9898", "", "1234", 1, 1, 1, chan, test);
- if (!cnf) {
- ast_test_status_update(test, "Build of test conference 9898 failed\n");
- ast_hangup(chan);
- return AST_TEST_FAIL;
- }
-
- node = ast_data_get(&query);
- if (!node) {
- ast_test_status_update(test, "Data query for test conference 9898 failed\n");
- dispose_conf(cnf);
- ast_hangup(chan);
- return AST_TEST_FAIL;
- }
-
- if (strcmp(ast_data_retrieve_string(node, "meetme/confno"), "9898")) {
- ast_test_status_update(test, "Query returned the wrong conference\n");
- dispose_conf(cnf);
- ast_hangup(chan);
- ast_data_free(node);
- return AST_TEST_FAIL;
- }
-
- ast_data_free(node);
- dispose_conf(cnf);
- ast_hangup(chan);
-
- return AST_TEST_PASS;
-}
-#endif
-
static int unload_module(void)
{
int res = 0;
@@ -8201,11 +8020,6 @@ static int unload_module(void)
res |= ast_unregister_application(slastation_app);
res |= ast_unregister_application(slatrunk_app);
-#ifdef TEST_FRAMEWORK
- AST_TEST_UNREGISTER(test_meetme_data_provider);
-#endif
- ast_data_unregister(NULL);
-
ast_devstate_prov_del("Meetme");
ast_devstate_prov_del("SLA");
@@ -8249,11 +8063,6 @@ static int load_module(void)
res |= ast_register_application_xml(slastation_app, sla_station_exec);
res |= ast_register_application_xml(slatrunk_app, sla_trunk_exec);
-#ifdef TEST_FRAMEWORK
- AST_TEST_REGISTER(test_meetme_data_provider);
-#endif
- ast_data_register_multiple(meetme_data_providers, ARRAY_LEN(meetme_data_providers));
-
res |= ast_devstate_prov_add("Meetme", meetmestate);
res |= ast_devstate_prov_add("SLA", sla_state);
diff --git a/apps/app_playback.c b/apps/app_playback.c
index 35900e8f7..7c895e362 100644
--- a/apps/app_playback.c
+++ b/apps/app_playback.c
@@ -322,7 +322,7 @@ static int say_date_generic(struct ast_channel *chan, time_t t,
if (format == NULL)
format = "";
- ast_localtime(&when, &tm, NULL);
+ ast_localtime(&when, &tm, timezonename);
snprintf(buf, sizeof(buf), "%s:%s:%04d%02d%02d%02d%02d.%02d-%d-%3d",
prefix,
format,
diff --git a/apps/app_queue.c b/apps/app_queue.c
index f158a4caa..762119e94 100644
--- a/apps/app_queue.c
+++ b/apps/app_queue.c
@@ -99,7 +99,6 @@
#include "asterisk/taskprocessor.h"
#include "asterisk/aoc.h"
#include "asterisk/callerid.h"
-#include "asterisk/data.h"
#include "asterisk/term.h"
#include "asterisk/dial.h"
#include "asterisk/stasis_channels.h"
@@ -793,16 +792,6 @@
<ref type="function">QUEUE_MEMBER_PENALTY</ref>
</see-also>
</function>
- <manager name="Queues" language="en_US">
- <synopsis>
- Queues.
- </synopsis>
- <syntax>
- </syntax>
- <description>
- <para>Show queues information.</para>
- </description>
- </manager>
<manager name="QueueStatus" language="en_US">
<synopsis>
Show queue status.
@@ -1009,6 +998,26 @@
<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">
@@ -1408,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";
@@ -7286,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);
@@ -8301,6 +8344,9 @@ stop:
} else if (qcontinue) {
reason = QUEUE_CONTINUE;
res = 0;
+ } else if (reason == QUEUE_LEAVEEMPTY) {
+ /* Return back to dialplan, don't hang up */
+ res = 0;
}
} else if (qe.valid_digits) {
ast_queue_log(args.queuename, ast_channel_uniqueid(chan), "NONE", "EXITWITHKEY",
@@ -9728,19 +9774,6 @@ static char *queue_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a
return __queues_show(NULL, a->fd, a->argc, a->argv);
}
-/*!\brief callback to display queues status in manager
- \addtogroup Group_AMI
- */
-static int manager_queues_show(struct mansession *s, const struct message *m)
-{
- static const char * const a[] = { "queue", "show" };
-
- __queues_show(s, -1, 2, a);
- astman_append(s, "\r\n\r\n"); /* Properly terminate Manager output */
-
- return RESULT_SUCCESS;
-}
-
static int manager_queue_rule_show(struct mansession *s, const struct message *m)
{
const char *rule = astman_get_header(m, "Rule");
@@ -9949,6 +9982,7 @@ static int manager_queues_status(struct mansession *s, const struct message *m)
"ConnectedLineNum: %s\r\n"
"ConnectedLineName: %s\r\n"
"Wait: %ld\r\n"
+ "Priority: %d\r\n"
"%s"
"\r\n",
q->name, pos++, ast_channel_name(qe->chan), ast_channel_uniqueid(qe->chan),
@@ -9956,7 +9990,7 @@ static int manager_queues_status(struct mansession *s, const struct message *m)
S_COR(ast_channel_caller(qe->chan)->id.name.valid, ast_channel_caller(qe->chan)->id.name.str, "unknown"),
S_COR(ast_channel_connected(qe->chan)->id.number.valid, ast_channel_connected(qe->chan)->id.number.str, "unknown"),
S_COR(ast_channel_connected(qe->chan)->id.name.valid, ast_channel_connected(qe->chan)->id.name.str, "unknown"),
- (long) (now - qe->start), idText);
+ (long) (now - qe->start), qe->prio, idText);
++q_items;
}
}
@@ -10259,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;
@@ -10446,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> */
@@ -10891,275 +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"),
-};
-
-/* struct call_queue astdata mapping. */
-#define DATA_EXPORT_CALL_QUEUE(MEMBER) \
- MEMBER(call_queue, name, AST_DATA_STRING) \
- MEMBER(call_queue, moh, AST_DATA_STRING) \
- MEMBER(call_queue, announce, AST_DATA_STRING) \
- MEMBER(call_queue, context, AST_DATA_STRING) \
- MEMBER(call_queue, membermacro, AST_DATA_STRING) \
- MEMBER(call_queue, membergosub, AST_DATA_STRING) \
- MEMBER(call_queue, defaultrule, AST_DATA_STRING) \
- MEMBER(call_queue, sound_next, AST_DATA_STRING) \
- MEMBER(call_queue, sound_thereare, AST_DATA_STRING) \
- MEMBER(call_queue, sound_calls, AST_DATA_STRING) \
- MEMBER(call_queue, queue_quantity1, AST_DATA_STRING) \
- MEMBER(call_queue, queue_quantity2, AST_DATA_STRING) \
- MEMBER(call_queue, sound_holdtime, AST_DATA_STRING) \
- MEMBER(call_queue, sound_minutes, AST_DATA_STRING) \
- MEMBER(call_queue, sound_minute, AST_DATA_STRING) \
- MEMBER(call_queue, sound_seconds, AST_DATA_STRING) \
- MEMBER(call_queue, sound_thanks, AST_DATA_STRING) \
- MEMBER(call_queue, sound_callerannounce, AST_DATA_STRING) \
- MEMBER(call_queue, sound_reporthold, AST_DATA_STRING) \
- MEMBER(call_queue, dead, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, ringinuse, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, announce_to_first_user, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, setinterfacevar, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, setqueuevar, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, setqueueentryvar, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, reportholdtime, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, wrapped, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, timeoutrestart, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, announceholdtime, AST_DATA_INTEGER) \
- MEMBER(call_queue, realtime, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, found, AST_DATA_BOOLEAN) \
- MEMBER(call_queue, announcepositionlimit, AST_DATA_INTEGER) \
- MEMBER(call_queue, announcefrequency, AST_DATA_SECONDS) \
- MEMBER(call_queue, minannouncefrequency, AST_DATA_SECONDS) \
- MEMBER(call_queue, periodicannouncefrequency, AST_DATA_SECONDS) \
- MEMBER(call_queue, numperiodicannounce, AST_DATA_INTEGER) \
- MEMBER(call_queue, randomperiodicannounce, AST_DATA_INTEGER) \
- MEMBER(call_queue, roundingseconds, AST_DATA_SECONDS) \
- MEMBER(call_queue, holdtime, AST_DATA_SECONDS) \
- MEMBER(call_queue, talktime, AST_DATA_SECONDS) \
- MEMBER(call_queue, callscompleted, AST_DATA_INTEGER) \
- MEMBER(call_queue, callsabandoned, AST_DATA_INTEGER) \
- MEMBER(call_queue, servicelevel, AST_DATA_INTEGER) \
- MEMBER(call_queue, callscompletedinsl, AST_DATA_INTEGER) \
- MEMBER(call_queue, monfmt, AST_DATA_STRING) \
- MEMBER(call_queue, montype, AST_DATA_INTEGER) \
- MEMBER(call_queue, count, AST_DATA_INTEGER) \
- MEMBER(call_queue, maxlen, AST_DATA_INTEGER) \
- MEMBER(call_queue, wrapuptime, AST_DATA_SECONDS) \
- MEMBER(call_queue, retry, AST_DATA_SECONDS) \
- MEMBER(call_queue, timeout, AST_DATA_SECONDS) \
- MEMBER(call_queue, weight, AST_DATA_INTEGER) \
- MEMBER(call_queue, autopause, AST_DATA_INTEGER) \
- MEMBER(call_queue, timeoutpriority, AST_DATA_INTEGER) \
- MEMBER(call_queue, rrpos, AST_DATA_INTEGER) \
- MEMBER(call_queue, memberdelay, AST_DATA_INTEGER) \
- MEMBER(call_queue, autofill, AST_DATA_INTEGER) \
- MEMBER(call_queue, members, AST_DATA_CONTAINER)
-
-AST_DATA_STRUCTURE(call_queue, DATA_EXPORT_CALL_QUEUE);
-
-/* struct member astdata mapping. */
-#define DATA_EXPORT_MEMBER(MEMBER) \
- MEMBER(member, interface, AST_DATA_STRING) \
- MEMBER(member, state_interface, AST_DATA_STRING) \
- MEMBER(member, membername, AST_DATA_STRING) \
- MEMBER(member, penalty, AST_DATA_INTEGER) \
- MEMBER(member, calls, AST_DATA_INTEGER) \
- MEMBER(member, dynamic, AST_DATA_INTEGER) \
- MEMBER(member, realtime, AST_DATA_INTEGER) \
- MEMBER(member, status, AST_DATA_INTEGER) \
- MEMBER(member, paused, AST_DATA_BOOLEAN) \
- MEMBER(member, rt_uniqueid, AST_DATA_STRING)
-
-AST_DATA_STRUCTURE(member, DATA_EXPORT_MEMBER);
-
-#define DATA_EXPORT_QUEUE_ENT(MEMBER) \
- MEMBER(queue_ent, moh, AST_DATA_STRING) \
- MEMBER(queue_ent, announce, AST_DATA_STRING) \
- MEMBER(queue_ent, context, AST_DATA_STRING) \
- MEMBER(queue_ent, digits, AST_DATA_STRING) \
- MEMBER(queue_ent, valid_digits, AST_DATA_INTEGER) \
- MEMBER(queue_ent, pos, AST_DATA_INTEGER) \
- MEMBER(queue_ent, prio, AST_DATA_INTEGER) \
- MEMBER(queue_ent, last_pos_said, AST_DATA_INTEGER) \
- MEMBER(queue_ent, last_periodic_announce_time, AST_DATA_INTEGER) \
- MEMBER(queue_ent, last_periodic_announce_sound, AST_DATA_INTEGER) \
- MEMBER(queue_ent, last_pos, AST_DATA_INTEGER) \
- MEMBER(queue_ent, opos, AST_DATA_INTEGER) \
- MEMBER(queue_ent, handled, AST_DATA_INTEGER) \
- MEMBER(queue_ent, pending, AST_DATA_INTEGER) \
- MEMBER(queue_ent, max_penalty, AST_DATA_INTEGER) \
- MEMBER(queue_ent, min_penalty, AST_DATA_INTEGER) \
- MEMBER(queue_ent, raise_penalty, AST_DATA_INTEGER) \
- MEMBER(queue_ent, linpos, AST_DATA_INTEGER) \
- MEMBER(queue_ent, linwrapped, AST_DATA_INTEGER) \
- MEMBER(queue_ent, start, AST_DATA_INTEGER) \
- MEMBER(queue_ent, expire, AST_DATA_INTEGER) \
- MEMBER(queue_ent, cancel_answered_elsewhere, AST_DATA_INTEGER)
-
-AST_DATA_STRUCTURE(queue_ent, DATA_EXPORT_QUEUE_ENT);
-
-/*!
- * \internal
- * \brief Add a queue to the data_root node.
- * \param[in] search The search tree.
- * \param[in] data_root The main result node.
- * \param[in] queue The queue to add.
- */
-static void queues_data_provider_get_helper(const struct ast_data_search *search,
- struct ast_data *data_root, struct call_queue *queue)
-{
- struct ao2_iterator im;
- struct member *member;
- struct queue_ent *qe;
- struct ast_data *data_queue, *data_members = NULL, *enum_node;
- struct ast_data *data_member, *data_callers = NULL, *data_caller, *data_caller_channel;
-
- data_queue = ast_data_add_node(data_root, "queue");
- if (!data_queue) {
- return;
- }
-
- ast_data_add_structure(call_queue, data_queue, queue);
-
- ast_data_add_str(data_queue, "strategy", int2strat(queue->strategy));
- ast_data_add_int(data_queue, "membercount", ao2_container_count(queue->members));
-
- /* announce position */
- enum_node = ast_data_add_node(data_queue, "announceposition");
- if (!enum_node) {
- return;
- }
- switch (queue->announceposition) {
- case ANNOUNCEPOSITION_LIMIT:
- ast_data_add_str(enum_node, "text", "limit");
- break;
- case ANNOUNCEPOSITION_MORE_THAN:
- ast_data_add_str(enum_node, "text", "more");
- break;
- case ANNOUNCEPOSITION_YES:
- ast_data_add_str(enum_node, "text", "yes");
- break;
- case ANNOUNCEPOSITION_NO:
- ast_data_add_str(enum_node, "text", "no");
- break;
- default:
- ast_data_add_str(enum_node, "text", "unknown");
- break;
- }
- ast_data_add_int(enum_node, "value", queue->announceposition);
-
- /* add queue members */
- im = ao2_iterator_init(queue->members, 0);
- while ((member = ao2_iterator_next(&im))) {
- if (!data_members) {
- data_members = ast_data_add_node(data_queue, "members");
- if (!data_members) {
- ao2_ref(member, -1);
- continue;
- }
- }
-
- data_member = ast_data_add_node(data_members, "member");
- if (!data_member) {
- ao2_ref(member, -1);
- continue;
- }
-
- ast_data_add_structure(member, data_member, member);
-
- ao2_ref(member, -1);
- }
- ao2_iterator_destroy(&im);
-
- /* include the callers inside the result. */
- if (queue->head) {
- for (qe = queue->head; qe; qe = qe->next) {
- if (!data_callers) {
- data_callers = ast_data_add_node(data_queue, "callers");
- if (!data_callers) {
- continue;
- }
- }
-
- data_caller = ast_data_add_node(data_callers, "caller");
- if (!data_caller) {
- continue;
- }
-
- ast_data_add_structure(queue_ent, data_caller, qe);
-
- /* add the caller channel. */
- data_caller_channel = ast_data_add_node(data_caller, "channel");
- if (!data_caller_channel) {
- continue;
- }
-
- ast_channel_data_add_structure(data_caller_channel, qe->chan, 1);
- }
- }
-
- /* if this queue doesn't match remove the added queue. */
- if (!ast_data_search_match(search, data_queue)) {
- ast_data_remove_node(data_root, data_queue);
- }
-}
-
-/*!
- * \internal
- * \brief Callback used to generate the queues tree.
- * \param[in] search The search pattern tree.
- * \retval NULL on error.
- * \retval non-NULL The generated tree.
- */
-static int queues_data_provider_get(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- struct ao2_iterator i;
- struct call_queue *queue, *queue_realtime = NULL;
- struct ast_config *cfg;
-
- /* load realtime queues. */
- cfg = ast_load_realtime_multientry("queues", "name LIKE", "%", SENTINEL);
- if (cfg) {
- char *category = NULL;
- while ((category = ast_category_browse(cfg, category))) {
- const char *queuename = ast_variable_retrieve(cfg, category, "name");
- if ((queue = find_load_queue_rt_friendly(queuename))) {
- queue_unref(queue);
- }
- }
- ast_config_destroy(cfg);
- }
-
- /* static queues. */
- i = ao2_iterator_init(queues, 0);
- while ((queue = ao2_iterator_next(&i))) {
- ao2_lock(queue);
- if (queue->realtime) {
- queue_realtime = find_load_queue_rt_friendly(queue->name);
- if (!queue_realtime) {
- ao2_unlock(queue);
- queue_unref(queue);
- continue;
- }
- queue_unref(queue_realtime);
- }
-
- queues_data_provider_get_helper(search, data_root, queue);
- ao2_unlock(queue);
- queue_unref(queue);
- }
- ao2_iterator_destroy(&i);
-
- return 0;
-}
-
-static const struct ast_data_handler queues_data_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = queues_data_provider_get
-};
-
-static const struct ast_data_entry queue_data_providers[] = {
- AST_DATA_ENTRY("asterisk/application/queue/list", &queues_data_provider),
+ AST_CLI_DEFINE(handle_queue_change_priority_caller, "Change priority caller on queue"),
};
static struct stasis_message_router *agent_router;
@@ -11191,7 +11052,6 @@ static int unload_module(void)
ast_cli_unregister_multiple(cli_queue, ARRAY_LEN(cli_queue));
ast_manager_unregister("QueueStatus");
- ast_manager_unregister("Queues");
ast_manager_unregister("QueueRule");
ast_manager_unregister("QueueSummary");
ast_manager_unregister("QueueAdd");
@@ -11203,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);
@@ -11219,8 +11080,6 @@ static int unload_module(void)
ast_custom_function_unregister(&queuewaitingcount_function);
ast_custom_function_unregister(&queuememberpenalty_function);
- ast_data_unregister(NULL);
-
device_state_sub = stasis_unsubscribe_and_join(device_state_sub);
ast_extension_state_del(0, extension_state_cb);
@@ -11299,8 +11158,6 @@ static int load_module(void)
reload_queue_members();
}
- ast_data_register_multiple(queue_data_providers, ARRAY_LEN(queue_data_providers));
-
err |= ast_cli_register_multiple(cli_queue, ARRAY_LEN(cli_queue));
err |= ast_register_application_xml(app, queue_exec);
err |= ast_register_application_xml(app_aqm, aqm_exec);
@@ -11309,7 +11166,6 @@ static int load_module(void)
err |= ast_register_application_xml(app_upqm, upqm_exec);
err |= ast_register_application_xml(app_ql, ql_exec);
err |= ast_register_application_xml(app_qupd, qupd_exec);
- err |= ast_manager_register_xml("Queues", 0, manager_queues_show);
err |= ast_manager_register_xml("QueueStatus", 0, manager_queues_status);
err |= ast_manager_register_xml("QueueSummary", 0, manager_queues_summary);
err |= ast_manager_register_xml("QueueAdd", EVENT_FLAG_AGENT, manager_add_queue_member);
@@ -11321,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);
diff --git a/apps/app_stream_echo.c b/apps/app_stream_echo.c
index 79d15917b..9695dcc87 100644
--- a/apps/app_stream_echo.c
+++ b/apps/app_stream_echo.c
@@ -108,7 +108,6 @@ static int stream_echo_write(struct ast_channel *chan, struct ast_frame *frame,
* we simply want to echo it back out onto the same stream number.
*/
num = ast_channel_is_multistream(chan) ? frame->stream_num : -1;
-
if (ast_write_stream(chan, num, frame)) {
return stream_echo_write_error(chan, frame, num);
}
@@ -120,7 +119,8 @@ static int stream_echo_write(struct ast_channel *chan, struct ast_frame *frame,
* Note, if the channel is not multi-stream capable then one_to_one will
* always be true, so it is safe to also not check for that here too.
*/
- if (one_to_one || ast_format_get_type(frame->subclass.format) != type) {
+ if (one_to_one || !frame->subclass.format ||
+ ast_format_get_type(frame->subclass.format) != type) {
return 0;
}
@@ -141,7 +141,6 @@ static int stream_echo_write(struct ast_channel *chan, struct ast_frame *frame,
for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {
struct ast_stream *stream = ast_stream_topology_get_stream(topology, i);
-
if (num != i && ast_stream_get_type(stream) == type) {
if (ast_write_stream(chan, i, frame)) {
return stream_echo_write_error(chan, frame, i);
@@ -171,7 +170,7 @@ static int stream_echo_perform(struct ast_channel *chan,
request_change = 0;
}
- f = ast_read(chan);
+ f = ast_read_stream(chan);
if (!f) {
return -1;
}
@@ -186,11 +185,13 @@ static int stream_echo_perform(struct ast_channel *chan,
if (f->frametype == AST_FRAME_CONTROL) {
if (f->subclass.integer == AST_CONTROL_VIDUPDATE && !update_sent) {
- if (stream_echo_write(chan, f, one_to_one, type)) {
+ if (stream_echo_write(chan, f, type, one_to_one)) {
ast_frfree(f);
return -1;
}
update_sent = 1;
+ } else if (f->subclass.integer == AST_CONTROL_SRCCHANGE) {
+ update_sent = 0;
} else if (f->subclass.integer == AST_CONTROL_STREAM_TOPOLOGY_CHANGED) {
update_sent = 0;
one_to_one = 0; /* Switch writing to one to many */
@@ -200,14 +201,14 @@ static int stream_echo_perform(struct ast_channel *chan,
.frametype = AST_FRAME_CONTROL,
.subclass.integer = AST_CONTROL_VIDUPDATE,
};
- stream_echo_write(chan, &frame, one_to_one, type);
+ stream_echo_write(chan, &frame, type, one_to_one);
update_sent = 1;
}
if (f->frametype != AST_FRAME_CONTROL &&
f->frametype != AST_FRAME_MODEM &&
f->frametype != AST_FRAME_NULL &&
- stream_echo_write(chan, f, one_to_one, type)) {
+ stream_echo_write(chan, f, type, one_to_one)) {
ast_frfree(f);
return -1;
}
diff --git a/apps/app_voicemail.c b/apps/app_voicemail.c
index 8f50e1106..0a07cc103 100644
--- a/apps/app_voicemail.c
+++ b/apps/app_voicemail.c
@@ -505,6 +505,7 @@ static int imapversion = 1;
static int expungeonhangup = 1;
static int imapgreetings = 0;
+static int imap_poll_logout = 0;
static char delimiter = '\0';
/* mail_open cannot be protected on a stream basis */
@@ -542,6 +543,8 @@ static int imap_retrieve_file (const char *dir, const int msgnum, const char *ma
static int imap_delete_old_greeting (char *dir, struct vm_state *vms);
static void check_quota(struct vm_state *vms, char *mailbox);
static int open_mailbox(struct vm_state *vms, struct ast_vm_user *vmu, int box);
+static void imap_logout(const char *mailbox_id);
+
struct vmstate {
struct vm_state *vms;
AST_LIST_ENTRY(vmstate) list;
@@ -3776,12 +3779,12 @@ static SQLHSTMT generic_prepare(struct odbc_obj *obj, void *data)
SQLHSTMT stmt;
res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (!SQL_SUCCEEDED(res)) {
ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
return NULL;
}
res = SQLPrepare(stmt, (unsigned char *) gps->sql, SQL_NTS);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (!SQL_SUCCEEDED(res)) {
ast_log(AST_LOG_WARNING, "SQL Prepare failed![%s]\n", gps->sql);
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return NULL;
@@ -3823,14 +3826,14 @@ static void odbc_update_msg_id(char *dir, int msg_num, char *msg_id)
* \brief Retrieves a file from an ODBC data store.
* \param dir the path to the file to be retrieved.
* \param msgnum the message number, such as within a mailbox folder.
- *
+ *
* This method is used by the RETRIEVE macro when mailboxes are stored in an ODBC back end.
* The purpose is to get the message from the database store to the local file system, so that the message may be played, or the information file may be read.
*
* The file is looked up by invoking a SQL on the odbc_table (default 'voicemessages') using the dir and msgnum input parameters.
* The output is the message information file with the name msgnum and the extension .txt
* and the message file with the extension of its format, in the directory with base file name of the msgnum.
- *
+ *
* \return 0 on success, -1 on error.
*/
static int retrieve_file(char *dir, int msgnum)
@@ -3843,7 +3846,7 @@ static int retrieve_file(char *dir, int msgnum)
SQLSMALLINT colcount = 0;
SQLHSTMT stmt;
char sql[PATH_MAX];
- char fmt[80]="";
+ char fmt[80] = "";
char *c;
char coltitle[256];
SQLSMALLINT collen;
@@ -3859,144 +3862,139 @@ static int retrieve_file(char *dir, int msgnum)
char msgnums[80];
char *argv[] = { dir, msgnums };
struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
-
struct odbc_obj *obj;
+
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- ast_copy_string(fmt, vmfmts, sizeof(fmt));
- c = strchr(fmt, '|');
- if (c)
- *c = '\0';
- if (!strcasecmp(fmt, "wav49"))
- strcpy(fmt, "WAV");
- snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
- if (msgnum > -1)
- make_file(fn, sizeof(fn), dir, msgnum);
- else
- ast_copy_string(fn, dir, sizeof(fn));
+ if (!obj) {
+ ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return -1;
+ }
- /* Create the information file */
- snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
-
- if (!(f = fopen(full_fn, "w+"))) {
- ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
- goto yuck;
- }
-
- snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
- snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLFetch(stmt);
- if (res == SQL_NO_DATA) {
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ ast_copy_string(fmt, vmfmts, sizeof(fmt));
+ c = strchr(fmt, '|');
+ if (c)
+ *c = '\0';
+ if (!strcasecmp(fmt, "wav49"))
+ strcpy(fmt, "WAV");
+
+ snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
+ if (msgnum > -1)
+ make_file(fn, sizeof(fn), dir, msgnum);
+ else
+ ast_copy_string(fn, dir, sizeof(fn));
+
+ /* Create the information file */
+ snprintf(full_fn, sizeof(full_fn), "%s.txt", fn);
+
+ if (!(f = fopen(full_fn, "w+"))) {
+ ast_log(AST_LOG_WARNING, "Failed to open/create '%s'\n", full_fn);
+ goto bail;
+ }
+
+ snprintf(full_fn, sizeof(full_fn), "%s.%s", fn, fmt);
+ snprintf(sql, sizeof(sql), "SELECT * FROM %s WHERE dir=? AND msgnum=?", odbc_table);
+
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto bail;
+ }
+
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ if (res != SQL_NO_DATA) {
ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
}
- fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
- if (fd < 0) {
- ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLNumResultCols(stmt, &colcount);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- if (f)
- fprintf(f, "[message]\n");
- for (x = 0; x < colcount; x++) {
- rowdata[0] = '\0';
- colsize = 0;
- collen = sizeof(coltitle);
- res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
- &datatype, &colsize, &decimaldigits, &nullable);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- if (!strcasecmp(coltitle, "recording")) {
- off_t offset;
- res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
- fdlen = colsize2;
- if (fd > -1) {
- char tmp[1]="";
- lseek(fd, fdlen - 1, SEEK_SET);
- if (write(fd, tmp, 1) != 1) {
- close(fd);
- fd = -1;
- continue;
- }
- /* Read out in small chunks */
- for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
- if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
- ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- } else {
- res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
- munmap(fdm, CHUNKSIZE);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- unlink(full_fn);
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- }
+ goto bail_with_handle;
+ }
+
+ fd = open(full_fn, O_RDWR | O_CREAT | O_TRUNC, VOICEMAIL_FILE_MODE);
+ if (fd < 0) {
+ ast_log(AST_LOG_WARNING, "Failed to write '%s': %s\n", full_fn, strerror(errno));
+ goto bail_with_handle;
+ }
+
+ res = SQLNumResultCols(stmt, &colcount);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Column Count error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ fprintf(f, "[message]\n");
+ for (x = 0; x < colcount; x++) {
+ rowdata[0] = '\0';
+ colsize = 0;
+ collen = sizeof(coltitle);
+ res = SQLDescribeCol(stmt, x + 1, (unsigned char *) coltitle, sizeof(coltitle), &collen,
+ &datatype, &colsize, &decimaldigits, &nullable);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Describe Column error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+ if (!strcasecmp(coltitle, "recording")) {
+ off_t offset;
+ res = SQLGetData(stmt, x + 1, SQL_BINARY, rowdata, 0, &colsize2);
+ fdlen = colsize2;
+ if (fd > -1) {
+ char tmp[1] = "";
+ lseek(fd, fdlen - 1, SEEK_SET);
+ if (write(fd, tmp, 1) != 1) {
+ close(fd);
+ fd = -1;
+ continue;
+ }
+ /* Read out in small chunks */
+ for (offset = 0; offset < colsize2; offset += CHUNKSIZE) {
+ if ((fdm = mmap(NULL, CHUNKSIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset)) == MAP_FAILED) {
+ ast_log(AST_LOG_WARNING, "Could not mmap the output file: %s (%d)\n", strerror(errno), errno);
+ goto bail_with_handle;
}
- if (truncate(full_fn, fdlen) < 0) {
- ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
+ res = SQLGetData(stmt, x + 1, SQL_BINARY, fdm, CHUNKSIZE, NULL);
+ munmap(fdm, CHUNKSIZE);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ unlink(full_fn);
+ goto bail_with_handle;
}
}
- } else {
- res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res == SQL_NULL_DATA) && (!strcasecmp(coltitle, "msg_id"))) {
- char msg_id[MSG_ID_LEN];
- generate_msg_id(msg_id);
- snprintf(rowdata, sizeof(rowdata), "%s", msg_id);
- odbc_update_msg_id(dir, msgnum, msg_id);
- } else if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
+ if (truncate(full_fn, fdlen) < 0) {
+ ast_log(LOG_WARNING, "Unable to truncate '%s': %s\n", full_fn, strerror(errno));
}
- if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir") && f)
- fprintf(f, "%s=%s\n", coltitle, rowdata);
+ }
+ } else {
+ res = SQLGetData(stmt, x + 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (res == SQL_NULL_DATA && !strcasecmp(coltitle, "msg_id")) {
+ char msg_id[MSG_ID_LEN];
+ generate_msg_id(msg_id);
+ snprintf(rowdata, sizeof(rowdata), "%s", msg_id);
+ odbc_update_msg_id(dir, msgnum, msg_id);
+ } else if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error! coltitle=%s\n[%s]\n\n", coltitle, sql);
+ goto bail_with_handle;
+ }
+ if (strcasecmp(coltitle, "msgnum") && strcasecmp(coltitle, "dir")) {
+ fprintf(f, "%s=%s\n", coltitle, rowdata);
}
}
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- } else
- ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
-yuck:
+ }
+
+bail_with_handle:
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+bail:
if (f)
fclose(f);
if (fd > -1)
close(fd);
+
+ ast_odbc_release_obj(obj);
+
return x - 1;
}
/*!
* \brief Determines the highest message number in use for a given user and mailbox folder.
- * \param vmu
+ * \param vmu
* \param dir the folder the mailbox folder to look for messages. Used to construct the SQL where clause.
*
* This method is used when mailboxes are stored in an ODBC back end.
@@ -4007,58 +4005,61 @@ yuck:
*/
static int last_message_index(struct ast_vm_user *vmu, char *dir)
{
- int x = 0;
+ int x = -1;
int res;
SQLHSTMT stmt;
char sql[PATH_MAX];
char rowdata[20];
char *argv[] = { dir };
struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
-
struct odbc_obj *obj;
+
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(sql, sizeof(sql), "SELECT msgnum FROM %s WHERE dir=? order by msgnum desc", odbc_table);
+ if (!obj) {
+ ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return -1;
+ }
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- ast_odbc_release_obj(obj);
- goto yuck;
+ snprintf(sql, sizeof(sql), "SELECT msgnum FROM %s WHERE dir=? order by msgnum desc", odbc_table);
+
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto bail;
+ }
+
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ if (res == SQL_NO_DATA) {
+ ast_log(AST_LOG_DEBUG, "Directory '%s' has no messages and therefore no index was retrieved.\n", dir);
+ } else {
+ ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
}
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- if (res == SQL_NO_DATA) {
- ast_log(AST_LOG_DEBUG, "Directory '%s' has no messages and therefore no index was retrieved.\n", dir);
- } else {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- }
+ goto bail_with_handle;
+ }
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- if (sscanf(rowdata, "%30d", &x) != 1)
- ast_log(AST_LOG_WARNING, "Failed to read message index!\n");
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- return x;
- } else
- ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
-yuck:
- return x - 1;
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ if (sscanf(rowdata, "%30d", &x) != 1) {
+ ast_log(AST_LOG_WARNING, "Failed to read message index!\n");
+ }
+
+bail_with_handle:
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+bail:
+ ast_odbc_release_obj(obj);
+
+ return x;
}
/*!
* \brief Determines if the specified message exists.
- * \param dir the folder the mailbox folder to look for messages.
+ * \param dir the folder the mailbox folder to look for messages.
* \param msgnum the message index to query for.
*
* This method is used when mailboxes are stored in an ODBC back end.
@@ -4075,39 +4076,43 @@ static int message_exists(char *dir, int msgnum)
char msgnums[20];
char *argv[] = { dir, msgnums };
struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
-
struct odbc_obj *obj;
+
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- if (sscanf(rowdata, "%30d", &x) != 1)
- ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
-yuck:
+ return 0;
+ }
+
+ snprintf(msgnums, sizeof(msgnums), "%d", msgnum);
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=? AND msgnum=?", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto bail;
+ }
+
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ if (sscanf(rowdata, "%30d", &x) != 1) {
+ ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
+ }
+
+bail_with_handle:
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+bail:
+ ast_odbc_release_obj(obj);
return x;
}
@@ -4122,48 +4127,50 @@ yuck:
*/
static int count_messages(struct ast_vm_user *vmu, char *dir)
{
- int x = 0;
+ int x = -1;
int res;
SQLHSTMT stmt;
char sql[PATH_MAX];
char rowdata[20];
char *argv[] = { dir };
struct generic_prepare_struct gps = { .sql = sql, .argc = 1, .argv = argv };
-
struct odbc_obj *obj;
+
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- goto yuck;
- }
- if (sscanf(rowdata, "%30d", &x) != 1)
- ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- return x;
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
-yuck:
- return x - 1;
+ return -1;
+ }
+
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir=?", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto bail;
+ }
+
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+
+ if (sscanf(rowdata, "%30d", &x) != 1) {
+ ast_log(AST_LOG_WARNING, "Failed to read message count!\n");
+ }
+bail_with_handle:
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+bail:
+ ast_odbc_release_obj(obj);
+ return x;
}
/*!
@@ -4173,7 +4180,7 @@ yuck:
*
* This method is used when mailboxes are stored in an ODBC back end.
* The specified message is directly deleted from the database 'voicemessages' table.
- *
+ *
* \return the value greater than zero on success to indicate the number of messages, less than zero on error.
*/
static void delete_file(const char *sdir, int smsg)
@@ -4185,21 +4192,25 @@ static void delete_file(const char *sdir, int smsg)
struct generic_prepare_struct gps = { .sql = sql, .argc = 2, .argv = argv };
struct odbc_obj *obj;
- argv[0] = ast_strdupa(sdir);
-
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(msgnums, sizeof(msgnums), "%d", smsg);
- snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt)
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- else
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
- return;
+ return;
+ }
+
+ argv[0] = ast_strdupa(sdir);
+
+ snprintf(msgnums, sizeof(msgnums), "%d", smsg);
+ snprintf(sql, sizeof(sql), "DELETE FROM %s WHERE dir=? AND msgnum=?", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ } else {
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ }
+ ast_odbc_release_obj(obj);
+
+ return;
}
/*!
@@ -4227,19 +4238,22 @@ static void copy_file(char *sdir, int smsg, char *ddir, int dmsg, char *dmailbox
generate_msg_id(msg_id);
delete_file(ddir, dmsg);
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(msgnums, sizeof(msgnums), "%d", smsg);
- snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
- snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, msg_id, context, macrocontext, callerid, origtime, duration, recording, flag, mailboxuser, mailboxcontext) SELECT ?,?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?", odbc_table, odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt)
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
- else
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
- return;
+ return;
+ }
+
+ snprintf(msgnums, sizeof(msgnums), "%d", smsg);
+ snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir, msgnum, msg_id, context, macrocontext, callerid, origtime, duration, recording, flag, mailboxuser, mailboxcontext) SELECT ?,?,?,context,macrocontext,callerid,origtime,duration,recording,flag,?,? FROM %s WHERE dir=? AND msgnum=?", odbc_table, odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt)
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s] (You probably don't have MySQL 4.1 or later installed)\n\n", sql);
+ else
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+
+ return;
}
struct insert_data {
@@ -4268,9 +4282,8 @@ static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
SQLHSTMT stmt;
res = SQLAllocHandle(SQL_HANDLE_STMT, obj->con, &stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (!SQL_SUCCEEDED(res)) {
ast_log(AST_LOG_WARNING, "SQL Alloc Handle failed!\n");
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return NULL;
}
@@ -4290,7 +4303,7 @@ static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
SQLBindParameter(stmt, 13, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, strlen(data->category), 0, (void *) data->category, 0, NULL);
}
res = SQLExecDirect(stmt, (unsigned char *) data->sql, SQL_NTS);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
+ if (!SQL_SUCCEEDED(res)) {
ast_log(AST_LOG_WARNING, "SQL Direct Execute failed!\n");
SQLFreeHandle(SQL_HANDLE_STMT, stmt);
return NULL;
@@ -4307,7 +4320,7 @@ static SQLHSTMT insert_data_cb(struct odbc_obj *obj, void *vdata)
* \param msgnum the message index for the message to be stored.
*
* This method is used when mailboxes are stored in an ODBC back end.
- * The message sound file and information file is looked up on the file system.
+ * The message sound file and information file is looked up on the file system.
* A SQL query is invoked to store the message into the (MySQL) database.
*
* \return the zero on success -1 on error.
@@ -4332,7 +4345,9 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
struct ast_flags config_flags = { CONFIG_FLAG_NOCACHE };
delete_file(dir, msgnum);
- if (!(obj = ast_odbc_request_obj(odbc_database, 0))) {
+
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
return -1;
}
@@ -4395,25 +4410,25 @@ static int store_file(const char *dir, const char *mailboxuser, const char *mail
ast_log(AST_LOG_WARNING, "Memory map failed for sound file '%s'!\n", full_fn);
res = -1;
break;
- }
+ }
idata.data = fdm;
idata.datalen = idata.indlen = fdlen;
- if (!ast_strlen_zero(idata.category))
- snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
+ if (!ast_strlen_zero(idata.category))
+ snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id,category) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
else
snprintf(sql, sizeof(sql), "INSERT INTO %s (dir,msgnum,recording,context,macrocontext,callerid,origtime,duration,mailboxuser,mailboxcontext,flag,msg_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)", odbc_table);
if ((stmt = ast_odbc_direct_execute(obj, insert_data_cb, &idata))) {
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
} else {
ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
res = -1;
}
} while (0);
- if (obj) {
- ast_odbc_release_obj(obj);
- }
+
+ ast_odbc_release_obj(obj);
+
if (valid_config(cfg))
ast_config_destroy(cfg);
if (fdm != MAP_FAILED)
@@ -4447,20 +4462,23 @@ static void rename_file(char *sdir, int smsg, char *mailboxuser, char *mailboxco
struct generic_prepare_struct gps = { .sql = sql, .argc = 6, .argv = argv };
delete_file(ddir, dmsg);
+
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- snprintf(msgnums, sizeof(msgnums), "%d", smsg);
- snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
- snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?", odbc_table);
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt)
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- else
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- ast_odbc_release_obj(obj);
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
- return;
+ return;
+ }
+
+ snprintf(msgnums, sizeof(msgnums), "%d", smsg);
+ snprintf(msgnumd, sizeof(msgnumd), "%d", dmsg);
+ snprintf(sql, sizeof(sql), "UPDATE %s SET dir=?, msgnum=?, mailboxuser=?, mailboxcontext=? WHERE dir=? AND msgnum=?", odbc_table);
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt)
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ else
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ ast_odbc_release_obj(obj);
+ return;
}
/*!
@@ -5660,17 +5678,48 @@ static void free_zone(struct vm_zone *z)
}
#ifdef ODBC_STORAGE
-static int inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs)
+
+static int count_messages_in_folder(struct odbc_obj *odbc, const char *context, const char *mailbox, const char *folder, int *messages)
{
- int x = -1;
int res;
- SQLHSTMT stmt = NULL;
char sql[PATH_MAX];
char rowdata[20];
+ SQLHSTMT stmt = NULL;
+ struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
+
+ if (!messages) {
+ return 0;
+ }
+
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
+ if (!(stmt = ast_odbc_prepare_and_execute(odbc, generic_prepare, &gps))) {
+ ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ return 1;
+ }
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ return 1;
+ }
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+ return 1;
+ }
+
+ *messages = atoi(rowdata);
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+ return 0;
+}
+
+static int inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int *oldmsgs)
+{
char tmp[PATH_MAX] = "";
- struct odbc_obj *obj = NULL;
+ struct odbc_obj *obj;
char *context;
- struct generic_prepare_struct gps = { .sql = sql, .argc = 0 };
if (newmsgs)
*newmsgs = 0;
@@ -5712,87 +5761,28 @@ static int inboxcount2(const char *mailbox, int *urgentmsgs, int *newmsgs, int *
} else
context = "default";
- if ((obj = ast_odbc_request_obj(odbc_database, 0))) {
- do {
- if (newmsgs) {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "INBOX");
- if (!(stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps))) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- break;
- }
- *newmsgs = atoi(rowdata);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- }
-
- if (oldmsgs) {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Old");
- if (!(stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps))) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- break;
- }
- SQLFreeHandle(SQL_HANDLE_STMT, stmt);
- *oldmsgs = atoi(rowdata);
- }
-
- if (urgentmsgs) {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, tmp, "Urgent");
- if (!(stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps))) {
- ast_log(LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- break;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- break;
- }
- *urgentmsgs = atoi(rowdata);
- }
-
- x = 0;
- } while (0);
- } else {
+ obj = ast_odbc_request_obj(odbc_database, 0);
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return -1;
}
- if (stmt) {
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- }
- if (obj) {
- ast_odbc_release_obj(obj);
+ if (count_messages_in_folder(obj, context, tmp, "INBOX", newmsgs)
+ || count_messages_in_folder(obj, context, tmp, "Old", oldmsgs)
+ || count_messages_in_folder(obj, context, tmp, "Urgent", urgentmsgs)) {
+ ast_log(AST_LOG_WARNING, "Failed to obtain message count for mailbox %s@%s\n",
+ tmp, context);
}
- return x;
+
+ ast_odbc_release_obj(obj);
+ return 0;
}
/*!
* \brief Gets the number of messages that exist in a mailbox folder.
* \param mailbox_id
* \param folder
- *
+ *
* This method is used when ODBC backend is used.
* \return The number of messages in this mailbox folder (zero or more).
*/
@@ -5819,37 +5809,39 @@ static int messagecount(const char *mailbox_id, const char *folder)
}
obj = ast_odbc_request_obj(odbc_database, 0);
- if (obj) {
- if (!strcmp(folder, "INBOX")) {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/INBOX' OR dir = '%s%s/%s/Urgent'", odbc_table, VM_SPOOL_DIR, context, mailbox, VM_SPOOL_DIR, context, mailbox);
- } else {
- snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
- }
- stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
- if (!stmt) {
- ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
- goto yuck;
- }
- res = SQLFetch(stmt);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- goto yuck;
- }
- res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
- if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
- ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- goto yuck;
- }
- nummsgs = atoi(rowdata);
- SQLFreeHandle (SQL_HANDLE_STMT, stmt);
- } else
+ if (!obj) {
ast_log(AST_LOG_WARNING, "Failed to obtain database object for '%s'!\n", odbc_database);
+ return 0;
+ }
+
+ if (!strcmp(folder, "INBOX")) {
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/INBOX' OR dir = '%s%s/%s/Urgent'", odbc_table, VM_SPOOL_DIR, context, mailbox, VM_SPOOL_DIR, context, mailbox);
+ } else {
+ snprintf(sql, sizeof(sql), "SELECT COUNT(*) FROM %s WHERE dir = '%s%s/%s/%s'", odbc_table, VM_SPOOL_DIR, context, mailbox, folder);
+ }
+
+ stmt = ast_odbc_prepare_and_execute(obj, generic_prepare, &gps);
+ if (!stmt) {
+ ast_log(AST_LOG_WARNING, "SQL Execute error!\n[%s]\n\n", sql);
+ goto bail;
+ }
+ res = SQLFetch(stmt);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Fetch error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+ res = SQLGetData(stmt, 1, SQL_CHAR, rowdata, sizeof(rowdata), NULL);
+ if (!SQL_SUCCEEDED(res)) {
+ ast_log(AST_LOG_WARNING, "SQL Get Data error!\n[%s]\n\n", sql);
+ goto bail_with_handle;
+ }
+ nummsgs = atoi(rowdata);
-yuck:
- if (obj)
- ast_odbc_release_obj(obj);
+bail_with_handle:
+ SQLFreeHandle(SQL_HANDLE_STMT, stmt);
+
+bail:
+ ast_odbc_release_obj(obj);
return nummsgs;
}
@@ -11072,7 +11064,7 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
int skipuser, int max_logins, int silent)
{
int useadsi = 0, valid = 0, logretries = 0;
- char password[AST_MAX_EXTENSION]="", *passptr;
+ char password[AST_MAX_EXTENSION], *passptr;
struct ast_vm_user vmus, *vmu = NULL;
/* If ADSI is supported, setup login screen */
@@ -11114,7 +11106,8 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
adsi_password(chan);
if (!ast_strlen_zero(prefix)) {
- char fullusername[80] = "";
+ char fullusername[80];
+
ast_copy_string(fullusername, prefix, sizeof(fullusername));
strncat(fullusername, mailbox, sizeof(fullusername) - 1 - strlen(fullusername));
ast_copy_string(mailbox, fullusername, mailbox_size);
@@ -11172,6 +11165,10 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
free_user(vmu);
return -1;
}
+ if (ast_waitstream(chan, "")) { /* Channel is hung up */
+ free_user(vmu);
+ return -1;
+ }
} else {
if (useadsi)
adsi_login(chan);
@@ -11181,10 +11178,6 @@ static int vm_authenticate(struct ast_channel *chan, char *mailbox, int mailbox_
return -1;
}
}
- if (ast_waitstream(chan, "")) { /* Channel is hung up */
- free_user(vmu);
- return -1;
- }
}
}
if (!valid && (logretries >= max_logins)) {
@@ -12303,6 +12296,9 @@ static int append_mailbox(const char *context, const char *box, const char *data
strcat(mailbox_full, context);
inboxcount2(mailbox_full, &urgent, &new, &old);
+#ifdef IMAP_STORAGE
+ imap_logout(mailbox_full);
+#endif
queue_mwi_event(NULL, mailbox_full, urgent, new, old);
return 0;
@@ -12908,153 +12904,18 @@ static struct ast_cli_entry cli_voicemail[] = {
AST_CLI_DEFINE(handle_voicemail_reload, "Reload voicemail configuration"),
};
-#ifdef IMAP_STORAGE
- #define DATA_EXPORT_VM_USERS(USER) \
- USER(ast_vm_user, context, AST_DATA_STRING) \
- USER(ast_vm_user, mailbox, AST_DATA_STRING) \
- USER(ast_vm_user, password, AST_DATA_PASSWORD) \
- USER(ast_vm_user, fullname, AST_DATA_STRING) \
- USER(ast_vm_user, email, AST_DATA_STRING) \
- USER(ast_vm_user, emailsubject, AST_DATA_STRING) \
- USER(ast_vm_user, emailbody, AST_DATA_STRING) \
- USER(ast_vm_user, pager, AST_DATA_STRING) \
- USER(ast_vm_user, serveremail, AST_DATA_STRING) \
- USER(ast_vm_user, fromstring, AST_DATA_STRING) \
- USER(ast_vm_user, language, AST_DATA_STRING) \
- USER(ast_vm_user, zonetag, AST_DATA_STRING) \
- USER(ast_vm_user, callback, AST_DATA_STRING) \
- USER(ast_vm_user, dialout, AST_DATA_STRING) \
- USER(ast_vm_user, uniqueid, AST_DATA_STRING) \
- USER(ast_vm_user, exit, AST_DATA_STRING) \
- USER(ast_vm_user, attachfmt, AST_DATA_STRING) \
- USER(ast_vm_user, flags, AST_DATA_UNSIGNED_INTEGER) \
- USER(ast_vm_user, saydurationm, AST_DATA_INTEGER) \
- USER(ast_vm_user, maxmsg, AST_DATA_INTEGER) \
- USER(ast_vm_user, maxdeletedmsg, AST_DATA_INTEGER) \
- USER(ast_vm_user, maxsecs, AST_DATA_INTEGER) \
- USER(ast_vm_user, imapuser, AST_DATA_STRING) \
- USER(ast_vm_user, imappassword, AST_DATA_STRING) \
- USER(ast_vm_user, imapvmshareid, AST_DATA_STRING) \
- USER(ast_vm_user, volgain, AST_DATA_DOUBLE)
-#else
- #define DATA_EXPORT_VM_USERS(USER) \
- USER(ast_vm_user, context, AST_DATA_STRING) \
- USER(ast_vm_user, mailbox, AST_DATA_STRING) \
- USER(ast_vm_user, password, AST_DATA_PASSWORD) \
- USER(ast_vm_user, fullname, AST_DATA_STRING) \
- USER(ast_vm_user, email, AST_DATA_STRING) \
- USER(ast_vm_user, emailsubject, AST_DATA_STRING) \
- USER(ast_vm_user, emailbody, AST_DATA_STRING) \
- USER(ast_vm_user, pager, AST_DATA_STRING) \
- USER(ast_vm_user, serveremail, AST_DATA_STRING) \
- USER(ast_vm_user, fromstring, AST_DATA_STRING) \
- USER(ast_vm_user, language, AST_DATA_STRING) \
- USER(ast_vm_user, zonetag, AST_DATA_STRING) \
- USER(ast_vm_user, callback, AST_DATA_STRING) \
- USER(ast_vm_user, dialout, AST_DATA_STRING) \
- USER(ast_vm_user, uniqueid, AST_DATA_STRING) \
- USER(ast_vm_user, exit, AST_DATA_STRING) \
- USER(ast_vm_user, attachfmt, AST_DATA_STRING) \
- USER(ast_vm_user, flags, AST_DATA_UNSIGNED_INTEGER) \
- USER(ast_vm_user, saydurationm, AST_DATA_INTEGER) \
- USER(ast_vm_user, maxmsg, AST_DATA_INTEGER) \
- USER(ast_vm_user, maxdeletedmsg, AST_DATA_INTEGER) \
- USER(ast_vm_user, maxsecs, AST_DATA_INTEGER) \
- USER(ast_vm_user, volgain, AST_DATA_DOUBLE)
-#endif
-
-AST_DATA_STRUCTURE(ast_vm_user, DATA_EXPORT_VM_USERS);
-
-#define DATA_EXPORT_VM_ZONES(ZONE) \
- ZONE(vm_zone, name, AST_DATA_STRING) \
- ZONE(vm_zone, timezone, AST_DATA_STRING) \
- ZONE(vm_zone, msg_format, AST_DATA_STRING)
-
-AST_DATA_STRUCTURE(vm_zone, DATA_EXPORT_VM_ZONES);
-
-/*!
- * \internal
- * \brief Add voicemail user to the data_root.
- * \param[in] search The search tree.
- * \param[in] data_root The main result node.
- * \param[in] user The voicemail user.
- */
-static int vm_users_data_provider_get_helper(const struct ast_data_search *search,
- struct ast_data *data_root, struct ast_vm_user *user)
-{
- struct ast_data *data_user, *data_zone;
- struct ast_data *data_state;
- struct vm_zone *zone = NULL;
- int urgentmsg = 0, newmsg = 0, oldmsg = 0;
- char ext_context[256] = "";
-
- data_user = ast_data_add_node(data_root, "user");
- if (!data_user) {
- return -1;
- }
-
- ast_data_add_structure(ast_vm_user, data_user, user);
-
- AST_LIST_LOCK(&zones);
- AST_LIST_TRAVERSE(&zones, zone, list) {
- if (!strcmp(zone->name, user->zonetag)) {
- break;
- }
- }
- AST_LIST_UNLOCK(&zones);
-
- /* state */
- data_state = ast_data_add_node(data_user, "state");
- if (!data_state) {
- return -1;
- }
- snprintf(ext_context, sizeof(ext_context), "%s@%s", user->mailbox, user->context);
- inboxcount2(ext_context, &urgentmsg, &newmsg, &oldmsg);
- ast_data_add_int(data_state, "urgentmsg", urgentmsg);
- ast_data_add_int(data_state, "newmsg", newmsg);
- ast_data_add_int(data_state, "oldmsg", oldmsg);
-
- if (zone) {
- data_zone = ast_data_add_node(data_user, "zone");
- ast_data_add_structure(vm_zone, data_zone, zone);
- }
-
- if (!ast_data_search_match(search, data_user)) {
- ast_data_remove_node(data_root, data_user);
- }
-
- return 0;
-}
-
-static int vm_users_data_provider_get(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- struct ast_vm_user *user;
-
- AST_LIST_LOCK(&users);
- AST_LIST_TRAVERSE(&users, user, list) {
- vm_users_data_provider_get_helper(search, data_root, user);
- }
- AST_LIST_UNLOCK(&users);
-
- return 0;
-}
-
-static const struct ast_data_handler vm_users_data_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = vm_users_data_provider_get
-};
-
-static const struct ast_data_entry vm_data_providers[] = {
- AST_DATA_ENTRY("asterisk/application/voicemail/list", &vm_users_data_provider)
-};
-
static void poll_subscribed_mailbox(struct mwi_sub *mwi_sub)
{
int new = 0, old = 0, urgent = 0;
inboxcount2(mwi_sub->mailbox, &urgent, &new, &old);
+#ifdef IMAP_STORAGE
+ if (imap_poll_logout) {
+ imap_logout(mwi_sub->mailbox);
+ }
+#endif
+
if (urgent != mwi_sub->old_urgent || new != mwi_sub->old_new || old != mwi_sub->old_old) {
mwi_sub->old_urgent = urgent;
mwi_sub->old_new = new;
@@ -13134,10 +12995,25 @@ static void imap_logout(const char *mailbox_id)
return;
}
+ ast_mutex_lock(&vms->lock);
vms->mailstream = mail_close(vms->mailstream);
+ ast_mutex_unlock(&vms->lock);
+
vmstate_delete(vms);
}
+static void imap_close_subscribed_mailboxes(void)
+{
+ struct mwi_sub *mwi_sub;
+
+ AST_RWLIST_RDLOCK(&mwi_subs);
+ AST_RWLIST_TRAVERSE(&mwi_subs, mwi_sub, entry) {
+ if (!ast_strlen_zero(mwi_sub->mailbox)) {
+ imap_logout(mwi_sub->mailbox);
+ }
+ }
+ AST_RWLIST_UNLOCK(&mwi_subs);
+}
#endif
static int handle_unsubscribe(void *datap)
@@ -13591,7 +13467,11 @@ static int actual_load_config(int reload, struct ast_config *cfg, struct ast_con
strcpy(listen_control_restart_key, DEFAULT_LISTEN_CONTROL_RESTART_KEY);
strcpy(listen_control_stop_key, DEFAULT_LISTEN_CONTROL_STOP_KEY);
- /* Free all the users structure */
+#ifdef IMAP_STORAGE
+ imap_close_subscribed_mailboxes();
+#endif
+
+ /* Free all the users structure */
free_vm_users();
/* Free all the zones structure */
@@ -13756,6 +13636,11 @@ static int actual_load_config(int reload, struct ast_config *cfg, struct ast_con
} else {
ast_copy_string(greetingfolder, imapfolder, sizeof(greetingfolder));
}
+ if ((val = ast_variable_retrieve(cfg, "general", "imap_poll_logout"))) {
+ imap_poll_logout = ast_true(val);
+ } else {
+ imap_poll_logout = 0;
+ }
/* There is some very unorthodox casting done here. This is due
* to the way c-client handles the argument passed in. It expects a
@@ -14961,7 +14846,6 @@ static int unload_module(void)
res |= ast_custom_function_unregister(&vm_info_acf);
res |= ast_manager_unregister("VoicemailUsersList");
res |= ast_manager_unregister("VoicemailRefresh");
- res |= ast_data_unregister(NULL);
#ifdef TEST_FRAMEWORK
res |= AST_TEST_UNREGISTER(test_voicemail_vmsayname);
res |= AST_TEST_UNREGISTER(test_voicemail_msgcount);
@@ -14985,6 +14869,9 @@ static int unload_module(void)
ast_unload_realtime("voicemail");
ast_unload_realtime("voicemail_data");
+#ifdef IMAP_STORAGE
+ imap_close_subscribed_mailboxes();
+#endif
free_vm_users();
free_vm_zones();
return res;
@@ -15068,7 +14955,6 @@ static int load_module(void)
}
ast_cli_register_multiple(cli_voicemail, ARRAY_LEN(cli_voicemail));
- ast_data_register_multiple(vm_data_providers, ARRAY_LEN(vm_data_providers));
#ifdef TEST_FRAMEWORK
ast_install_vm_test_functions(vm_test_create_user, vm_test_destroy_user);
diff --git a/apps/confbridge/conf_config_parser.c b/apps/confbridge/conf_config_parser.c
index cc8fcfe5d..bfd9f4f56 100644
--- a/apps/confbridge/conf_config_parser.c
+++ b/apps/confbridge/conf_config_parser.c
@@ -450,6 +450,16 @@
</enumlist>
</description>
</configOption>
+ <configOption name="video_update_discard" default="2000">
+ <synopsis>Sets the amount of time in milliseconds after sending a video update to discard subsequent video updates</synopsis>
+ <description><para>
+ Sets the amount of time in milliseconds after sending a video update request
+ that subsequent video updates should be discarded. This means that if we
+ send a video update we will discard any other video update requests until
+ after the configured amount of time has elapsed. This prevents flooding of
+ video update requests from clients.
+ </para></description>
+ </configOption>
<configOption name="template">
<synopsis>When using the CONFBRIDGE dialplan function, use a bridge profile as a template for creating a new temporary profile</synopsis>
</configOption>
@@ -1652,6 +1662,8 @@ static char *handle_cli_confbridge_show_bridge_profile(struct ast_cli_entry *e,
break;
}
+ ast_cli(a->fd,"Video Update Discard: %u\n", b_profile.video_update_discard);
+
ast_cli(a->fd,"sound_only_person: %s\n", conf_get_sound(CONF_SOUND_ONLY_PERSON, b_profile.sounds));
ast_cli(a->fd,"sound_only_one: %s\n", conf_get_sound(CONF_SOUND_ONLY_ONE, b_profile.sounds));
ast_cli(a->fd,"sound_has_joined: %s\n", conf_get_sound(CONF_SOUND_HAS_JOINED, b_profile.sounds));
@@ -2220,6 +2232,7 @@ int conf_load_config(void)
aco_option_register(&cfg_info, "regcontext", ACO_EXACT, bridge_types, NULL, OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct bridge_profile, regcontext));
aco_option_register(&cfg_info, "language", ACO_EXACT, bridge_types, "en", OPT_CHAR_ARRAY_T, 0, CHARFLDSET(struct bridge_profile, language));
aco_option_register_custom(&cfg_info, "^sound_", ACO_REGEX, bridge_types, NULL, sound_option_handler, 0);
+ aco_option_register(&cfg_info, "video_update_discard", ACO_EXACT, bridge_types, "2000", OPT_UINT_T, 0, FLDSET(struct bridge_profile, video_update_discard));
/* This option should only be used with the CONFBRIDGE dialplan function */
aco_option_register_custom(&cfg_info, "template", ACO_EXACT, bridge_types, NULL, bridge_template_handler, 0);
diff --git a/apps/confbridge/include/confbridge.h b/apps/confbridge/include/confbridge.h
index cf30d5c62..adf9b867d 100644
--- a/apps/confbridge/include/confbridge.h
+++ b/apps/confbridge/include/confbridge.h
@@ -218,6 +218,7 @@ struct bridge_profile {
unsigned int mix_interval; /*!< The internal mixing interval used by the bridge. When set to 0 the bridgewill use a default interval. */
struct bridge_profile_sounds *sounds;
char regcontext[AST_MAX_CONTEXT];
+ unsigned int video_update_discard; /*!< Amount of time after sending a video update request that subsequent requests should be discarded */
};
/*! \brief The structure that represents a conference bridge */
diff --git a/bridges/bridge_native_rtp.c b/bridges/bridge_native_rtp.c
index 4af93bfca..02b27e123 100644
--- a/bridges/bridge_native_rtp.c
+++ b/bridges/bridge_native_rtp.c
@@ -44,76 +44,214 @@
#include "asterisk/frame.h"
#include "asterisk/rtp_engine.h"
-/*! \brief Internal structure which contains information about bridged RTP channels */
-struct native_rtp_bridge_data {
+/*! \brief Internal structure which contains bridged RTP channel hook data */
+struct native_rtp_framehook_data {
/*! \brief Framehook used to intercept certain control frames */
int id;
/*! \brief Set when this framehook has been detached */
unsigned int detached;
};
-/*! \brief Internal helper function which gets all RTP information (glue and instances) relating to the given channels */
-static enum ast_rtp_glue_result native_rtp_bridge_get(struct ast_channel *c0, struct ast_channel *c1, struct ast_rtp_glue **glue0,
- struct ast_rtp_glue **glue1, struct ast_rtp_instance **instance0, struct ast_rtp_instance **instance1,
- struct ast_rtp_instance **vinstance0, struct ast_rtp_instance **vinstance1)
+struct rtp_glue_stream {
+ /*! \brief RTP instance */
+ struct ast_rtp_instance *instance;
+ /*! \brief glue result */
+ enum ast_rtp_glue_result result;
+};
+
+struct rtp_glue_data {
+ /*!
+ * \brief glue callbacks
+ *
+ * \note The glue data is considered valid if cb is not NULL.
+ */
+ struct ast_rtp_glue *cb;
+ struct rtp_glue_stream audio;
+ struct rtp_glue_stream video;
+ /*! Combined glue result of both bridge channels. */
+ enum ast_rtp_glue_result result;
+};
+
+/*! \brief Internal structure which contains instance information about bridged RTP channels */
+struct native_rtp_bridge_channel_data {
+ /*! \brief Channel's hook data */
+ struct native_rtp_framehook_data *hook_data;
+ /*!
+ * \brief Glue callbacks to bring remote channel streams back to Asterisk.
+ * \note NULL if channel streams are local.
+ */
+ struct ast_rtp_glue *remote_cb;
+ /*! \brief Channel's cached RTP glue information */
+ struct rtp_glue_data glue;
+};
+
+static void rtp_glue_data_init(struct rtp_glue_data *glue)
{
- enum ast_rtp_glue_result audio_glue0_res;
- enum ast_rtp_glue_result video_glue0_res;
- enum ast_rtp_glue_result audio_glue1_res;
- enum ast_rtp_glue_result video_glue1_res;
+ glue->cb = NULL;
+ glue->audio.instance = NULL;
+ glue->audio.result = AST_RTP_GLUE_RESULT_FORBID;
+ glue->video.instance = NULL;
+ glue->video.result = AST_RTP_GLUE_RESULT_FORBID;
+ glue->result = AST_RTP_GLUE_RESULT_FORBID;
+}
- if (!(*glue0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type)) ||
- !(*glue1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type))) {
- return AST_RTP_GLUE_RESULT_FORBID;
+static void rtp_glue_data_destroy(struct rtp_glue_data *glue)
+{
+ if (!glue) {
+ return;
}
+ ao2_cleanup(glue->audio.instance);
+ ao2_cleanup(glue->video.instance);
+}
+
+static void rtp_glue_data_reset(struct rtp_glue_data *glue)
+{
+ rtp_glue_data_destroy(glue);
+ rtp_glue_data_init(glue);
+}
+
+static void native_rtp_bridge_channel_data_free(struct native_rtp_bridge_channel_data *data)
+{
+ ast_debug(2, "Destroying channel tech_pvt data %p\n", data);
- audio_glue0_res = (*glue0)->get_rtp_info(c0, instance0);
- video_glue0_res = (*glue0)->get_vrtp_info ? (*glue0)->get_vrtp_info(c0, vinstance0) : AST_RTP_GLUE_RESULT_FORBID;
+ /*
+ * hook_data will probably already have been unreferenced by the framehook detach
+ * and the pointer set to null.
+ */
+ ao2_cleanup(data->hook_data);
- audio_glue1_res = (*glue1)->get_rtp_info(c1, instance1);
- video_glue1_res = (*glue1)->get_vrtp_info ? (*glue1)->get_vrtp_info(c1, vinstance1) : AST_RTP_GLUE_RESULT_FORBID;
+ rtp_glue_data_reset(&data->glue);
+ ast_free(data);
+}
+
+static struct native_rtp_bridge_channel_data *native_rtp_bridge_channel_data_alloc(void)
+{
+ struct native_rtp_bridge_channel_data *data;
+
+ data = ast_calloc(1, sizeof(*data));
+ if (data) {
+ rtp_glue_data_init(&data->glue);
+ }
+ return data;
+}
+
+/*!
+ * \internal
+ * \brief Helper function which gets all RTP information (glue and instances) relating to the given channels
+ *
+ * \retval 0 on success.
+ * \retval -1 on error.
+ */
+static int rtp_glue_data_get(struct ast_channel *c0, struct rtp_glue_data *glue0,
+ struct ast_channel *c1, struct rtp_glue_data *glue1)
+{
+ struct ast_rtp_glue *cb0;
+ struct ast_rtp_glue *cb1;
+ enum ast_rtp_glue_result combined_result;
+
+ cb0 = ast_rtp_instance_get_glue(ast_channel_tech(c0)->type);
+ cb1 = ast_rtp_instance_get_glue(ast_channel_tech(c1)->type);
+ if (!cb0 || !cb1) {
+ /* One or both channels doesn't have any RTP glue registered. */
+ return -1;
+ }
+
+ /* The glue callbacks bump the RTP instance refcounts for us. */
+
+ glue0->cb = cb0;
+ glue0->audio.result = cb0->get_rtp_info(c0, &glue0->audio.instance);
+ glue0->video.result = cb0->get_vrtp_info
+ ? cb0->get_vrtp_info(c0, &glue0->video.instance) : AST_RTP_GLUE_RESULT_FORBID;
+
+ glue1->cb = cb1;
+ glue1->audio.result = cb1->get_rtp_info(c1, &glue1->audio.instance);
+ glue1->video.result = cb1->get_vrtp_info
+ ? cb1->get_vrtp_info(c1, &glue1->video.instance) : AST_RTP_GLUE_RESULT_FORBID;
+
+ /*
+ * Now determine the combined glue result.
+ */
/* Apply any limitations on direct media bridging that may be present */
- if (audio_glue0_res == audio_glue1_res && audio_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
- if ((*glue0)->allow_rtp_remote && !((*glue0)->allow_rtp_remote(c0, *instance1))) {
+ if (glue0->audio.result == glue1->audio.result && glue1->audio.result == AST_RTP_GLUE_RESULT_REMOTE) {
+ if (glue0->cb->allow_rtp_remote && !glue0->cb->allow_rtp_remote(c0, glue1->audio.instance)) {
/* If the allow_rtp_remote indicates that remote isn't allowed, revert to local bridge */
- audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
- } else if ((*glue1)->allow_rtp_remote && !((*glue1)->allow_rtp_remote(c1, *instance0))) {
- audio_glue0_res = audio_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+ glue0->audio.result = glue1->audio.result = AST_RTP_GLUE_RESULT_LOCAL;
+ } else if (glue1->cb->allow_rtp_remote && !glue1->cb->allow_rtp_remote(c1, glue0->audio.instance)) {
+ glue0->audio.result = glue1->audio.result = AST_RTP_GLUE_RESULT_LOCAL;
}
}
- if (video_glue0_res == video_glue1_res && video_glue1_res == AST_RTP_GLUE_RESULT_REMOTE) {
- if ((*glue0)->allow_vrtp_remote && !((*glue0)->allow_vrtp_remote(c0, *instance1))) {
- /* if the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */
- video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
- } else if ((*glue1)->allow_vrtp_remote && !((*glue1)->allow_vrtp_remote(c1, *instance0))) {
- video_glue0_res = video_glue1_res = AST_RTP_GLUE_RESULT_LOCAL;
+ if (glue0->video.result == glue1->video.result && glue1->video.result == AST_RTP_GLUE_RESULT_REMOTE) {
+ if (glue0->cb->allow_vrtp_remote && !glue0->cb->allow_vrtp_remote(c0, glue1->video.instance)) {
+ /* If the allow_vrtp_remote indicates that remote isn't allowed, revert to local bridge */
+ glue0->video.result = glue1->video.result = AST_RTP_GLUE_RESULT_LOCAL;
+ } else if (glue1->cb->allow_vrtp_remote && !glue1->cb->allow_vrtp_remote(c1, glue0->video.instance)) {
+ glue0->video.result = glue1->video.result = AST_RTP_GLUE_RESULT_LOCAL;
}
}
/* If we are carrying video, and both sides are not going to remotely bridge... fail the native bridge */
- if (video_glue0_res != AST_RTP_GLUE_RESULT_FORBID
- && (audio_glue0_res != AST_RTP_GLUE_RESULT_REMOTE
- || video_glue0_res != AST_RTP_GLUE_RESULT_REMOTE)) {
- audio_glue0_res = AST_RTP_GLUE_RESULT_FORBID;
+ if (glue0->video.result != AST_RTP_GLUE_RESULT_FORBID
+ && (glue0->audio.result != AST_RTP_GLUE_RESULT_REMOTE
+ || glue0->video.result != AST_RTP_GLUE_RESULT_REMOTE)) {
+ glue0->audio.result = AST_RTP_GLUE_RESULT_FORBID;
}
- if (video_glue1_res != AST_RTP_GLUE_RESULT_FORBID
- && (audio_glue1_res != AST_RTP_GLUE_RESULT_REMOTE
- || video_glue1_res != AST_RTP_GLUE_RESULT_REMOTE)) {
- audio_glue1_res = AST_RTP_GLUE_RESULT_FORBID;
+ if (glue1->video.result != AST_RTP_GLUE_RESULT_FORBID
+ && (glue1->audio.result != AST_RTP_GLUE_RESULT_REMOTE
+ || glue1->video.result != AST_RTP_GLUE_RESULT_REMOTE)) {
+ glue1->audio.result = AST_RTP_GLUE_RESULT_FORBID;
}
/* The order of preference is: forbid, local, and remote. */
- if (audio_glue0_res == AST_RTP_GLUE_RESULT_FORBID ||
- audio_glue1_res == AST_RTP_GLUE_RESULT_FORBID) {
+ if (glue0->audio.result == AST_RTP_GLUE_RESULT_FORBID
+ || glue1->audio.result == AST_RTP_GLUE_RESULT_FORBID) {
/* If any sort of bridge is forbidden just completely bail out and go back to generic bridging */
- return AST_RTP_GLUE_RESULT_FORBID;
- } else if (audio_glue0_res == AST_RTP_GLUE_RESULT_LOCAL ||
- audio_glue1_res == AST_RTP_GLUE_RESULT_LOCAL) {
- return AST_RTP_GLUE_RESULT_LOCAL;
+ combined_result = AST_RTP_GLUE_RESULT_FORBID;
+ } else if (glue0->audio.result == AST_RTP_GLUE_RESULT_LOCAL
+ || glue1->audio.result == AST_RTP_GLUE_RESULT_LOCAL) {
+ combined_result = AST_RTP_GLUE_RESULT_LOCAL;
} else {
- return AST_RTP_GLUE_RESULT_REMOTE;
+ combined_result = AST_RTP_GLUE_RESULT_REMOTE;
+ }
+ glue0->result = combined_result;
+ glue1->result = combined_result;
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Get the current RTP native bridge combined glue result.
+ * \since 15.0.0
+ *
+ * \param c0 First bridge channel
+ * \param c1 Second bridge channel
+ *
+ * \note Both channels must be locked when calling this function.
+ *
+ * \return Current combined glue result.
+ */
+static enum ast_rtp_glue_result rtp_glue_get_current_combined_result(struct ast_channel *c0,
+ struct ast_channel *c1)
+{
+ struct rtp_glue_data glue_a;
+ struct rtp_glue_data glue_b;
+ struct rtp_glue_data *glue0;
+ struct rtp_glue_data *glue1;
+ enum ast_rtp_glue_result combined_result;
+
+ rtp_glue_data_init(&glue_a);
+ glue0 = &glue_a;
+ rtp_glue_data_init(&glue_b);
+ glue1 = &glue_b;
+ if (rtp_glue_data_get(c0, glue0, c1, glue1)) {
+ return AST_RTP_GLUE_RESULT_FORBID;
}
+
+ combined_result = glue0->result;
+ rtp_glue_data_destroy(glue0);
+ rtp_glue_data_destroy(glue1);
+ return combined_result;
}
/*!
@@ -129,52 +267,91 @@ static void native_rtp_bridge_start(struct ast_bridge *bridge, struct ast_channe
{
struct ast_bridge_channel *bc0 = AST_LIST_FIRST(&bridge->channels);
struct ast_bridge_channel *bc1 = AST_LIST_LAST(&bridge->channels);
- enum ast_rtp_glue_result native_type = AST_RTP_GLUE_RESULT_FORBID;
- struct ast_rtp_glue *glue0, *glue1;
- RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, tinstance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, tinstance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_format_cap *, cap0, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);
- RAII_VAR(struct ast_format_cap *, cap1, ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT), ao2_cleanup);
+ struct native_rtp_bridge_channel_data *data0;
+ struct native_rtp_bridge_channel_data *data1;
+ struct rtp_glue_data *glue0;
+ struct rtp_glue_data *glue1;
+ struct ast_format_cap *cap0;
+ struct ast_format_cap *cap1;
+ enum ast_rtp_glue_result native_type;
if (bc0 == bc1) {
return;
}
+ data0 = bc0->tech_pvt;
+ data1 = bc1->tech_pvt;
+ if (!data0 || !data1) {
+ /* Not all channels are joined with the bridge tech yet */
+ return;
+ }
+ glue0 = &data0->glue;
+ glue1 = &data1->glue;
ast_channel_lock_both(bc0->chan, bc1->chan);
- if (!bc0->suspended && !bc1->suspended) {
- native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);
+
+ if (!glue0->cb || !glue1->cb) {
+ /*
+ * Somebody doesn't have glue data so the bridge isn't running
+ *
+ * Actually neither side should have glue data.
+ */
+ ast_assert(!glue0->cb && !glue1->cb);
+
+ if (rtp_glue_data_get(bc0->chan, glue0, bc1->chan, glue1)) {
+ /*
+ * This might happen if one of the channels got masqueraded
+ * at a critical time. It's a bit of a stretch even then
+ * since the channel is in a bridge.
+ */
+ goto done;
+ }
}
+ ast_debug(2, "Bridge '%s'. Tech starting '%s' and '%s' with target '%s'\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan),
+ target ? ast_channel_name(target) : "none");
+
+ native_type = glue0->result;
+
switch (native_type) {
case AST_RTP_GLUE_RESULT_LOCAL:
- if (ast_rtp_instance_get_engine(instance0)->local_bridge) {
- ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, instance1);
+ if (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge) {
+ ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge(glue0->audio.instance, glue1->audio.instance);
}
- if (ast_rtp_instance_get_engine(instance1)->local_bridge) {
- ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, instance0);
+ if (ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge) {
+ ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge(glue1->audio.instance, glue0->audio.instance);
}
- ast_rtp_instance_set_bridged(instance0, instance1);
- ast_rtp_instance_set_bridged(instance1, instance0);
+ ast_rtp_instance_set_bridged(glue0->audio.instance, glue1->audio.instance);
+ ast_rtp_instance_set_bridged(glue1->audio.instance, glue0->audio.instance);
ast_verb(4, "Locally RTP bridged '%s' and '%s' in stack\n",
ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));
break;
-
case AST_RTP_GLUE_RESULT_REMOTE:
- if (glue0->get_codec) {
- glue0->get_codec(bc0->chan, cap0);
+ cap0 = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ cap1 = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!cap0 || !cap1) {
+ ao2_cleanup(cap0);
+ ao2_cleanup(cap1);
+ break;
}
- if (glue1->get_codec) {
- glue1->get_codec(bc1->chan, cap1);
+
+ if (glue0->cb->get_codec) {
+ glue0->cb->get_codec(bc0->chan, cap0);
+ }
+ if (glue1->cb->get_codec) {
+ glue1->cb->get_codec(bc1->chan, cap1);
}
- /* If we have a target, it's the channel that received the UNHOLD or UPDATE_RTP_PEER frame and was told to resume */
+ /*
+ * If we have a target, it's the channel that received the UNHOLD or
+ * UPDATE_RTP_PEER frame and was told to resume
+ */
if (!target) {
- glue0->update_peer(bc0->chan, instance1, vinstance1, tinstance1, cap1, 0);
- glue1->update_peer(bc1->chan, instance0, vinstance0, tinstance0, cap0, 0);
+ /* Send both channels to remote */
+ data0->remote_cb = glue0->cb;
+ data1->remote_cb = glue1->cb;
+ glue0->cb->update_peer(bc0->chan, glue1->audio.instance, glue1->video.instance, NULL, cap1, 0);
+ glue1->cb->update_peer(bc1->chan, glue0->audio.instance, glue0->video.instance, NULL, cap0, 0);
ast_verb(4, "Remotely bridged '%s' and '%s' - media will flow directly between them\n",
ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));
} else {
@@ -184,51 +361,121 @@ static void native_rtp_bridge_start(struct ast_bridge *bridge, struct ast_channe
* already set up to handle the new media path or will have its own set of updates independent
* of this pass.
*/
+ ast_debug(2, "Bridge '%s'. Sending '%s' back to remote\n",
+ bridge->uniqueid, ast_channel_name(target));
if (bc0->chan == target) {
- glue0->update_peer(bc0->chan, instance1, vinstance1, tinstance1, cap1, 0);
+ data0->remote_cb = glue0->cb;
+ glue0->cb->update_peer(bc0->chan, glue1->audio.instance, glue1->video.instance, NULL, cap1, 0);
} else {
- glue1->update_peer(bc1->chan, instance0, vinstance0, tinstance0, cap0, 0);
+ data1->remote_cb = glue1->cb;
+ glue1->cb->update_peer(bc1->chan, glue0->audio.instance, glue0->video.instance, NULL, cap0, 0);
}
}
+
+ ao2_cleanup(cap0);
+ ao2_cleanup(cap1);
break;
case AST_RTP_GLUE_RESULT_FORBID:
break;
}
+ if (native_type != AST_RTP_GLUE_RESULT_REMOTE) {
+ /* Bring any remaining channels back to us. */
+ if (data0->remote_cb) {
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan));
+ data0->remote_cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
+ data0->remote_cb = NULL;
+ }
+ if (data1->remote_cb) {
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc1->chan));
+ data1->remote_cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
+ data1->remote_cb = NULL;
+ }
+ }
+
+done:
ast_channel_unlock(bc0->chan);
ast_channel_unlock(bc1->chan);
}
+/*!
+ * \internal
+ * \brief Stop native RTP bridging of two channels
+ *
+ * \param bridge The bridge that had native RTP bridging happening on it
+ * \param target If remote RTP bridging, the channel that is held.
+ *
+ * \note The first channel to leave the bridge triggers the cleanup for both channels
+ */
static void native_rtp_bridge_stop(struct ast_bridge *bridge, struct ast_channel *target)
{
struct ast_bridge_channel *bc0 = AST_LIST_FIRST(&bridge->channels);
struct ast_bridge_channel *bc1 = AST_LIST_LAST(&bridge->channels);
- enum ast_rtp_glue_result native_type;
- struct ast_rtp_glue *glue0, *glue1 = NULL;
- RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);
+ struct native_rtp_bridge_channel_data *data0;
+ struct native_rtp_bridge_channel_data *data1;
+ struct rtp_glue_data *glue0;
+ struct rtp_glue_data *glue1;
if (bc0 == bc1) {
return;
}
+ data0 = bc0->tech_pvt;
+ data1 = bc1->tech_pvt;
+ if (!data0 || !data1) {
+ /* Not all channels are joined with the bridge tech */
+ return;
+ }
+ glue0 = &data0->glue;
+ glue1 = &data1->glue;
+
+ ast_debug(2, "Bridge '%s'. Tech stopping '%s' and '%s' with target '%s'\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan),
+ target ? ast_channel_name(target) : "none");
+
+ if (!glue0->cb || !glue1->cb) {
+ /*
+ * Somebody doesn't have glue data so the bridge isn't running
+ *
+ * Actually neither side should have glue data.
+ */
+ ast_assert(!glue0->cb && !glue1->cb);
+ /* At most one channel can be left at the remote endpoint here. */
+ ast_assert(!data0->remote_cb || !data1->remote_cb);
+
+ /* Bring selected channel streams back to us */
+ if (data0->remote_cb && (!target || target == bc0->chan)) {
+ ast_channel_lock(bc0->chan);
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan));
+ data0->remote_cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
+ data0->remote_cb = NULL;
+ ast_channel_unlock(bc0->chan);
+ }
+ if (data1->remote_cb && (!target || target == bc1->chan)) {
+ ast_channel_lock(bc1->chan);
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc1->chan));
+ data1->remote_cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
+ data1->remote_cb = NULL;
+ ast_channel_unlock(bc1->chan);
+ }
+ return;
+ }
ast_channel_lock_both(bc0->chan, bc1->chan);
- native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1, &instance0, &instance1, &vinstance0, &vinstance1);
- switch (native_type) {
+ switch (glue0->result) {
case AST_RTP_GLUE_RESULT_LOCAL:
- if (ast_rtp_instance_get_engine(instance0)->local_bridge) {
- ast_rtp_instance_get_engine(instance0)->local_bridge(instance0, NULL);
- }
- if (instance1 && ast_rtp_instance_get_engine(instance1)->local_bridge) {
- ast_rtp_instance_get_engine(instance1)->local_bridge(instance1, NULL);
+ if (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge) {
+ ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge(glue0->audio.instance, NULL);
}
- ast_rtp_instance_set_bridged(instance0, NULL);
- if (instance1) {
- ast_rtp_instance_set_bridged(instance1, NULL);
+ if (ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge) {
+ ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge(glue1->audio.instance, NULL);
}
+ ast_rtp_instance_set_bridged(glue0->audio.instance, NULL);
+ ast_rtp_instance_set_bridged(glue1->audio.instance, NULL);
break;
case AST_RTP_GLUE_RESULT_REMOTE:
if (target) {
@@ -236,10 +483,38 @@ static void native_rtp_bridge_stop(struct ast_bridge *bridge, struct ast_channel
* If a target was provided, it is being put on hold and should expect to
* receive media from Asterisk instead of what it was previously connected to.
*/
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(target));
if (bc0->chan == target) {
- glue0->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
+ data0->remote_cb = NULL;
+ glue0->cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
+ } else {
+ data1->remote_cb = NULL;
+ glue1->cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
+ }
+ } else {
+ data0->remote_cb = NULL;
+ data1->remote_cb = NULL;
+ /*
+ * XXX We don't want to bring back the channels if we are
+ * switching to T.38. We have received a reinvite on one channel
+ * and we will be sending a reinvite on the other to start T.38.
+ * If we bring the streams back now we confuse the chan_pjsip
+ * channel driver processing the incoming T.38 reinvite with
+ * reinvite glare. I think this is really a bug in chan_pjsip
+ * that this exception case is working around.
+ */
+ if (rtp_glue_get_current_combined_result(bc0->chan, bc1->chan)
+ != AST_RTP_GLUE_RESULT_FORBID) {
+ ast_debug(2, "Bridge '%s'. Bringing back '%s' and '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan),
+ ast_channel_name(bc1->chan));
+ glue0->cb->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
+ glue1->cb->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
} else {
- glue1->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
+ ast_debug(2, "Bridge '%s'. Skip bringing back '%s' and '%s' to us\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan),
+ ast_channel_name(bc1->chan));
}
}
break;
@@ -247,10 +522,8 @@ static void native_rtp_bridge_stop(struct ast_bridge *bridge, struct ast_channel
break;
}
- if (!target && native_type != AST_RTP_GLUE_RESULT_FORBID) {
- glue0->update_peer(bc0->chan, NULL, NULL, NULL, NULL, 0);
- glue1->update_peer(bc1->chan, NULL, NULL, NULL, NULL, 0);
- }
+ rtp_glue_data_reset(glue0);
+ rtp_glue_data_reset(glue1);
ast_debug(2, "Discontinued RTP bridging of '%s' and '%s' - media will flow through Asterisk core\n",
ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));
@@ -259,11 +532,15 @@ static void native_rtp_bridge_stop(struct ast_bridge *bridge, struct ast_channel
ast_channel_unlock(bc1->chan);
}
-/*! \brief Frame hook that is called to intercept hold/unhold */
-static struct ast_frame *native_rtp_framehook(struct ast_channel *chan, struct ast_frame *f, enum ast_framehook_event event, void *data)
+/*!
+ * \internal
+ * \brief Frame hook that is called to intercept hold/unhold
+ */
+static struct ast_frame *native_rtp_framehook(struct ast_channel *chan,
+ struct ast_frame *f, enum ast_framehook_event event, void *data)
{
RAII_VAR(struct ast_bridge *, bridge, NULL, ao2_cleanup);
- struct native_rtp_bridge_data *native_data = data;
+ struct native_rtp_framehook_data *native_data = data;
if (!f || (event != AST_FRAMEHOOK_EVENT_WRITE)) {
return f;
@@ -293,39 +570,49 @@ static struct ast_frame *native_rtp_framehook(struct ast_channel *chan, struct a
}
ast_bridge_unlock(bridge);
ast_channel_lock(chan);
-
}
return f;
}
-/*! \brief Callback function which informs upstream if we are consuming a frame of a specific type */
+/*!
+ * \internal
+ * \brief Callback function which informs upstream if we are consuming a frame of a specific type
+ */
static int native_rtp_framehook_consume(void *data, enum ast_frame_type type)
{
return (type == AST_FRAME_CONTROL ? 1 : 0);
}
-/*! \brief Internal helper function which checks whether the channels are compatible with our native bridging */
+/*!
+ * \internal
+ * \brief Internal helper function which checks whether a channel is compatible with our native bridging
+ */
static int native_rtp_bridge_capable(struct ast_channel *chan)
{
return !ast_channel_has_hook_requiring_audio(chan);
}
+/*!
+ * \internal
+ * \brief Internal helper function which checks whether both channels are compatible with our native bridging
+ */
static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct ast_bridge_channel *bc0, struct ast_bridge_channel *bc1)
{
enum ast_rtp_glue_result native_type;
- struct ast_rtp_glue *glue0;
- struct ast_rtp_glue *glue1;
- RAII_VAR(struct ast_rtp_instance *, instance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_rtp_instance *, vinstance1, NULL, ao2_cleanup);
- RAII_VAR(struct ast_format_cap *, cap0, NULL, ao2_cleanup);
- RAII_VAR(struct ast_format_cap *, cap1, NULL, ao2_cleanup);
int read_ptime0;
int read_ptime1;
int write_ptime0;
int write_ptime1;
+ struct rtp_glue_data glue_a;
+ struct rtp_glue_data glue_b;
+ RAII_VAR(struct ast_format_cap *, cap0, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_format_cap *, cap1, NULL, ao2_cleanup);
+ RAII_VAR(struct rtp_glue_data *, glue0, NULL, rtp_glue_data_destroy);
+ RAII_VAR(struct rtp_glue_data *, glue1, NULL, rtp_glue_data_destroy);
+
+ ast_debug(1, "Bridge '%s'. Checking compatability for channels '%s' and '%s'\n",
+ bridge->uniqueid, ast_channel_name(bc0->chan), ast_channel_name(bc1->chan));
if (!native_rtp_bridge_capable(bc0->chan)) {
ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has features which prevent it\n",
@@ -339,8 +626,17 @@ static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct
return 0;
}
- native_type = native_rtp_bridge_get(bc0->chan, bc1->chan, &glue0, &glue1,
- &instance0, &instance1, &vinstance0, &vinstance1);
+ rtp_glue_data_init(&glue_a);
+ glue0 = &glue_a;
+ rtp_glue_data_init(&glue_b);
+ glue1 = &glue_b;
+ if (rtp_glue_data_get(bc0->chan, glue0, bc1->chan, glue1)) {
+ ast_debug(1, "Bridge '%s' can not use native RTP bridge as could not get details\n",
+ bridge->uniqueid);
+ return 0;
+ }
+ native_type = glue0->result;
+
if (native_type == AST_RTP_GLUE_RESULT_FORBID) {
ast_debug(1, "Bridge '%s' can not use native RTP bridge as it was forbidden while getting details\n",
bridge->uniqueid);
@@ -348,25 +644,25 @@ static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct
}
if (ao2_container_count(bc0->features->dtmf_hooks)
- && ast_rtp_instance_dtmf_mode_get(instance0)) {
+ && ast_rtp_instance_dtmf_mode_get(glue0->audio.instance)) {
ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",
bridge->uniqueid, ast_channel_name(bc0->chan));
return 0;
}
if (ao2_container_count(bc1->features->dtmf_hooks)
- && ast_rtp_instance_dtmf_mode_get(instance1)) {
+ && ast_rtp_instance_dtmf_mode_get(glue1->audio.instance)) {
ast_debug(1, "Bridge '%s' can not use native RTP bridge as channel '%s' has DTMF hooks\n",
bridge->uniqueid, ast_channel_name(bc1->chan));
return 0;
}
if (native_type == AST_RTP_GLUE_RESULT_LOCAL
- && (ast_rtp_instance_get_engine(instance0)->local_bridge
- != ast_rtp_instance_get_engine(instance1)->local_bridge
- || (ast_rtp_instance_get_engine(instance0)->dtmf_compatible
- && !ast_rtp_instance_get_engine(instance0)->dtmf_compatible(bc0->chan,
- instance0, bc1->chan, instance1)))) {
+ && (ast_rtp_instance_get_engine(glue0->audio.instance)->local_bridge
+ != ast_rtp_instance_get_engine(glue1->audio.instance)->local_bridge
+ || (ast_rtp_instance_get_engine(glue0->audio.instance)->dtmf_compatible
+ && !ast_rtp_instance_get_engine(glue0->audio.instance)->dtmf_compatible(bc0->chan,
+ glue0->audio.instance, bc1->chan, glue1->audio.instance)))) {
ast_debug(1, "Bridge '%s' can not use local native RTP bridge as local bridge or DTMF is not compatible\n",
bridge->uniqueid);
return 0;
@@ -379,11 +675,11 @@ static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct
}
/* Make sure that codecs match */
- if (glue0->get_codec) {
- glue0->get_codec(bc0->chan, cap0);
+ if (glue0->cb->get_codec) {
+ glue0->cb->get_codec(bc0->chan, cap0);
}
- if (glue1->get_codec) {
- glue1->get_codec(bc1->chan, cap1);
+ if (glue1->cb->get_codec) {
+ glue1->cb->get_codec(bc1->chan, cap1);
}
if (ast_format_cap_count(cap0) != 0
&& ast_format_cap_count(cap1) != 0
@@ -413,6 +709,10 @@ static int native_rtp_bridge_compatible_check(struct ast_bridge *bridge, struct
return 1;
}
+/*!
+ * \internal
+ * \brief Called by the bridge core "compatible' callback
+ */
static int native_rtp_bridge_compatible(struct ast_bridge *bridge)
{
struct ast_bridge_channel *bc0;
@@ -437,10 +737,13 @@ static int native_rtp_bridge_compatible(struct ast_bridge *bridge)
return is_compatible;
}
-/*! \brief Helper function which adds frame hook to bridge channel */
+/*!
+ * \internal
+ * \brief Helper function which adds frame hook to bridge channel
+ */
static int native_rtp_bridge_framehook_attach(struct ast_bridge_channel *bridge_channel)
{
- struct native_rtp_bridge_data *data = ao2_alloc(sizeof(*data), NULL);
+ struct native_rtp_bridge_channel_data *data = bridge_channel->tech_pvt;
static struct ast_framehook_interface hook = {
.version = AST_FRAMEHOOK_INTERFACE_VERSION,
.event_cb = native_rtp_framehook,
@@ -449,45 +752,82 @@ static int native_rtp_bridge_framehook_attach(struct ast_bridge_channel *bridge_
.disable_inheritance = 1,
};
- if (!data) {
+ ast_assert(data->hook_data == NULL);
+ data->hook_data = ao2_alloc_options(sizeof(*data->hook_data), NULL,
+ AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!data->hook_data) {
return -1;
}
+ ast_debug(2, "Bridge '%s'. Attaching hook data %p to '%s'\n",
+ bridge_channel->bridge->uniqueid, data, ast_channel_name(bridge_channel->chan));
+
ast_channel_lock(bridge_channel->chan);
- hook.data = ao2_bump(data);
- data->id = ast_framehook_attach(bridge_channel->chan, &hook);
+ /* We're giving 1 ref to the framehook and keeping the one from the alloc for ourselves */
+ hook.data = ao2_bump(data->hook_data);
+ data->hook_data->id = ast_framehook_attach(bridge_channel->chan, &hook);
ast_channel_unlock(bridge_channel->chan);
- if (data->id < 0) {
- /* We need to drop both the reference we hold, and the one the framehook would hold */
- ao2_ref(data, -2);
+ if (data->hook_data->id < 0) {
+ /*
+ * We need to drop both the reference we hold in data,
+ * and the one the framehook would hold.
+ */
+ ao2_ref(data->hook_data, -2);
+ data->hook_data = NULL;
+
return -1;
}
- bridge_channel->tech_pvt = data;
-
return 0;
}
-/*! \brief Helper function which removes frame hook from bridge channel */
+/*!
+ * \internal
+ * \brief Helper function which removes frame hook from bridge channel
+ */
static void native_rtp_bridge_framehook_detach(struct ast_bridge_channel *bridge_channel)
{
- RAII_VAR(struct native_rtp_bridge_data *, data, bridge_channel->tech_pvt, ao2_cleanup);
+ struct native_rtp_bridge_channel_data *data = bridge_channel->tech_pvt;
- if (!data) {
+ if (!data || !data->hook_data) {
return;
}
+ ast_debug(2, "Bridge '%s'. Detaching hook data %p from '%s'\n",
+ bridge_channel->bridge->uniqueid, data->hook_data, ast_channel_name(bridge_channel->chan));
+
ast_channel_lock(bridge_channel->chan);
- ast_framehook_detach(bridge_channel->chan, data->id);
- data->detached = 1;
+ ast_framehook_detach(bridge_channel->chan, data->hook_data->id);
+ data->hook_data->detached = 1;
ast_channel_unlock(bridge_channel->chan);
- bridge_channel->tech_pvt = NULL;
+ ao2_cleanup(data->hook_data);
+ data->hook_data = NULL;
}
+/*!
+ * \internal
+ * \brief Called by the bridge core 'join' callback for each channel joining he bridge
+ */
static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
- native_rtp_bridge_framehook_detach(bridge_channel);
+ ast_debug(2, "Bridge '%s'. Channel '%s' is joining bridge tech\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));
+
+ ast_assert(bridge_channel->tech_pvt == NULL);
+
+ if (bridge_channel->suspended) {
+ /* The channel will rejoin when it is unsuspended */
+ return 0;
+ }
+
+ bridge_channel->tech_pvt = native_rtp_bridge_channel_data_alloc();
+ if (!bridge_channel->tech_pvt) {
+ return -1;
+ }
+
if (native_rtp_bridge_framehook_attach(bridge_channel)) {
+ native_rtp_bridge_channel_data_free(bridge_channel->tech_pvt);
+ bridge_channel->tech_pvt = NULL;
return -1;
}
@@ -495,15 +835,46 @@ static int native_rtp_bridge_join(struct ast_bridge *bridge, struct ast_bridge_c
return 0;
}
+/*!
+ * \internal
+ * \brief Add the channel back into the bridge
+ */
static void native_rtp_bridge_unsuspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
+ ast_debug(2, "Bridge '%s'. Channel '%s' is unsuspended back to bridge tech\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));
native_rtp_bridge_join(bridge, bridge_channel);
}
+/*!
+ * \internal
+ * \brief Leave the bridge
+ */
static void native_rtp_bridge_leave(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
{
+ ast_debug(2, "Bridge '%s'. Channel '%s' is leaving bridge tech\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));
+
+ if (!bridge_channel->tech_pvt) {
+ return;
+ }
+
native_rtp_bridge_framehook_detach(bridge_channel);
native_rtp_bridge_stop(bridge, NULL);
+
+ native_rtp_bridge_channel_data_free(bridge_channel->tech_pvt);
+ bridge_channel->tech_pvt = NULL;
+}
+
+/*!
+ * \internal
+ * \brief Suspend the channel from the bridge
+ */
+static void native_rtp_bridge_suspend(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel)
+{
+ ast_debug(2, "Bridge '%s'. Channel '%s' is suspending from bridge tech\n",
+ bridge->uniqueid, ast_channel_name(bridge_channel->chan));
+ native_rtp_bridge_leave(bridge, bridge_channel);
}
static int native_rtp_bridge_write(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
@@ -548,7 +919,7 @@ static struct ast_bridge_technology native_rtp_bridge = {
.join = native_rtp_bridge_join,
.unsuspend = native_rtp_bridge_unsuspend,
.leave = native_rtp_bridge_leave,
- .suspend = native_rtp_bridge_leave,
+ .suspend = native_rtp_bridge_suspend,
.write = native_rtp_bridge_write,
.compatible = native_rtp_bridge_compatible,
};
diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c
index ae877eb6e..c5428a854 100644
--- a/bridges/bridge_softmix.c
+++ b/bridges/bridge_softmix.c
@@ -524,15 +524,32 @@ static int append_all_streams(struct ast_stream_topology *dest,
const struct ast_stream_topology *source)
{
int i;
+ int dest_index = 0;
for (i = 0; i < ast_stream_topology_get_count(source); ++i) {
struct ast_stream *clone;
+ int added = 0;
clone = ast_stream_clone(ast_stream_topology_get_stream(source, i), NULL);
if (!clone) {
return -1;
}
- if (ast_stream_topology_append_stream(dest, clone) < 0) {
+
+ /* If we can reuse an existing removed stream then do so */
+ while (dest_index < ast_stream_topology_get_count(dest)) {
+ struct ast_stream *stream = ast_stream_topology_get_stream(dest, dest_index);
+
+ dest_index++;
+
+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
+ ast_stream_topology_set_stream(dest, dest_index - 1, clone);
+ added = 1;
+ break;
+ }
+ }
+
+ /* If no removed stream exists that we took the place of append the stream */
+ if (!added && ast_stream_topology_append_stream(dest, clone) < 0) {
ast_stream_free(clone);
return -1;
}
@@ -600,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;
}
@@ -701,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);
}
@@ -967,6 +985,8 @@ static void softmix_bridge_write_voice(struct ast_bridge *bridge, struct ast_bri
*/
static int softmix_bridge_write_control(struct ast_bridge *bridge, struct ast_bridge_channel *bridge_channel, struct ast_frame *frame)
{
+ struct softmix_bridge_data *softmix_data = bridge->tech_pvt;
+
/*
* XXX Softmix needs to use channel roles to determine what to
* do with control frames.
@@ -974,7 +994,11 @@ static int softmix_bridge_write_control(struct ast_bridge *bridge, struct ast_br
switch (frame->subclass.integer) {
case AST_CONTROL_VIDUPDATE:
- ast_bridge_queue_everyone_else(bridge, NULL, frame);
+ if (!bridge->softmix.video_mode.video_update_discard ||
+ ast_tvdiff_ms(ast_tvnow(), softmix_data->last_video_update) > bridge->softmix.video_mode.video_update_discard) {
+ ast_bridge_queue_everyone_else(bridge, NULL, frame);
+ softmix_data->last_video_update = ast_tvnow();
+ }
break;
default:
break;
@@ -1964,9 +1988,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;
@@ -2033,6 +2057,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;
+ }
}
}
diff --git a/bridges/bridge_softmix/include/bridge_softmix_internal.h b/bridges/bridge_softmix/include/bridge_softmix_internal.h
index 9daae4ce8..f93e66391 100644
--- a/bridges/bridge_softmix/include/bridge_softmix_internal.h
+++ b/bridges/bridge_softmix/include/bridge_softmix_internal.h
@@ -198,6 +198,8 @@ struct softmix_bridge_data {
* (does not guarantee success)
*/
unsigned int binaural_init;
+ /*! The last time a video update was sent into the bridge */
+ struct timeval last_video_update;
};
struct softmix_mixing_array {
diff --git a/channels/chan_dahdi.c b/channels/chan_dahdi.c
index 72fbe6e0c..4f717ffac 100644
--- a/channels/chan_dahdi.c
+++ b/channels/chan_dahdi.c
@@ -119,7 +119,6 @@
#include "asterisk/devicestate.h"
#include "asterisk/paths.h"
#include "asterisk/ccss.h"
-#include "asterisk/data.h"
#include "asterisk/features_config.h"
#include "asterisk/bridge.h"
#include "asterisk/stasis_channels.h"
@@ -791,78 +790,6 @@ const char * const subnames[] = {
"Threeway"
};
-#define DATA_EXPORT_DAHDI_PVT(MEMBER) \
- MEMBER(dahdi_pvt, cid_rxgain, AST_DATA_DOUBLE) \
- MEMBER(dahdi_pvt, rxgain, AST_DATA_DOUBLE) \
- MEMBER(dahdi_pvt, txgain, AST_DATA_DOUBLE) \
- MEMBER(dahdi_pvt, txdrc, AST_DATA_DOUBLE) \
- MEMBER(dahdi_pvt, rxdrc, AST_DATA_DOUBLE) \
- MEMBER(dahdi_pvt, adsi, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, answeronpolarityswitch, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, busydetect, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, callreturn, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, callwaiting, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, callwaitingcallerid, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, cancallforward, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, canpark, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, confirmanswer, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, destroy, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, didtdd, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, dialednone, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, dialing, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, digital, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, dnd, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, echobreak, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, echocanbridged, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, echocanon, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, faxhandled, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, usefaxbuffers, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, bufferoverrideinuse, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, firstradio, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, hanguponpolarityswitch, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, hardwaredtmf, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, hidecallerid, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, hidecalleridname, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, ignoredtmf, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, immediate, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, inalarm, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, mate, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, outgoing, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, permcallwaiting, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, priindication_oob, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, priexclusive, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, pulse, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, pulsedial, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, restartpending, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, restrictcid, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, threewaycalling, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, transfer, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, use_callerid, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, use_callingpres, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, usedistinctiveringdetection, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, dahditrcallerid, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, transfertobusy, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, mwimonitor_neon, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, mwimonitor_fsk, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, mwimonitor_rpas, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, mwimonitoractive, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, mwisendactive, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, inservice, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, locallyblocked, AST_DATA_UNSIGNED_INTEGER) \
- MEMBER(dahdi_pvt, remotelyblocked, AST_DATA_UNSIGNED_INTEGER) \
- MEMBER(dahdi_pvt, manages_span_alarms, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, use_smdi, AST_DATA_BOOLEAN) \
- MEMBER(dahdi_pvt, context, AST_DATA_STRING) \
- MEMBER(dahdi_pvt, defcontext, AST_DATA_STRING) \
- MEMBER(dahdi_pvt, description, AST_DATA_STRING) \
- MEMBER(dahdi_pvt, exten, AST_DATA_STRING) \
- MEMBER(dahdi_pvt, language, AST_DATA_STRING) \
- MEMBER(dahdi_pvt, mohinterpret, AST_DATA_STRING) \
- MEMBER(dahdi_pvt, mohsuggest, AST_DATA_STRING) \
- MEMBER(dahdi_pvt, parkinglot, AST_DATA_STRING)
-
-AST_DATA_STRUCTURE(dahdi_pvt, DATA_EXPORT_DAHDI_PVT);
-
static struct dahdi_pvt *iflist = NULL; /*!< Main interface list start */
static struct dahdi_pvt *ifend = NULL; /*!< Main interface list end */
@@ -17313,7 +17240,6 @@ static int __unload_module(void)
ast_manager_unregister("PRIDebugFileSet");
ast_manager_unregister("PRIDebugFileUnset");
#endif /* defined(HAVE_PRI) */
- ast_data_unregister(NULL);
ast_channel_unregister(&dahdi_tech);
/* Hangup all interfaces if they have an owner */
@@ -19356,163 +19282,6 @@ static int setup_dahdi(int reload)
}
/*!
- * \internal
- * \brief Callback used to generate the dahdi status tree.
- * \param[in] search The search pattern tree.
- * \retval NULL on error.
- * \retval non-NULL The generated tree.
- */
-static int dahdi_status_data_provider_get(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- int ctl, res, span;
- struct ast_data *data_span, *data_alarms;
- struct dahdi_spaninfo s;
-
- ctl = open("/dev/dahdi/ctl", O_RDWR);
- if (ctl < 0) {
- ast_log(LOG_ERROR, "No DAHDI found. Unable to open /dev/dahdi/ctl: %s\n", strerror(errno));
- return -1;
- }
- for (span = 1; span < DAHDI_MAX_SPANS; ++span) {
- s.spanno = span;
- res = ioctl(ctl, DAHDI_SPANSTAT, &s);
- if (res) {
- continue;
- }
-
- data_span = ast_data_add_node(data_root, "span");
- if (!data_span) {
- continue;
- }
- ast_data_add_str(data_span, "description", s.desc);
-
- /* insert the alarms status */
- data_alarms = ast_data_add_node(data_span, "alarms");
- if (!data_alarms) {
- continue;
- }
-
- ast_data_add_bool(data_alarms, "BLUE", s.alarms & DAHDI_ALARM_BLUE);
- ast_data_add_bool(data_alarms, "YELLOW", s.alarms & DAHDI_ALARM_YELLOW);
- ast_data_add_bool(data_alarms, "RED", s.alarms & DAHDI_ALARM_RED);
- ast_data_add_bool(data_alarms, "LOOPBACK", s.alarms & DAHDI_ALARM_LOOPBACK);
- ast_data_add_bool(data_alarms, "RECOVER", s.alarms & DAHDI_ALARM_RECOVER);
- ast_data_add_bool(data_alarms, "NOTOPEN", s.alarms & DAHDI_ALARM_NOTOPEN);
-
- ast_data_add_int(data_span, "irqmisses", s.irqmisses);
- ast_data_add_int(data_span, "bpviol", s.bpvcount);
- ast_data_add_int(data_span, "crc4", s.crc4count);
- ast_data_add_str(data_span, "framing", s.lineconfig & DAHDI_CONFIG_D4 ? "D4" :
- s.lineconfig & DAHDI_CONFIG_ESF ? "ESF" :
- s.lineconfig & DAHDI_CONFIG_CCS ? "CCS" :
- "CAS");
- ast_data_add_str(data_span, "coding", s.lineconfig & DAHDI_CONFIG_B8ZS ? "B8ZS" :
- s.lineconfig & DAHDI_CONFIG_HDB3 ? "HDB3" :
- s.lineconfig & DAHDI_CONFIG_AMI ? "AMI" :
- "Unknown");
- ast_data_add_str(data_span, "options", s.lineconfig & DAHDI_CONFIG_CRC4 ?
- s.lineconfig & DAHDI_CONFIG_NOTOPEN ? "CRC4/YEL" : "CRC4" :
- s.lineconfig & DAHDI_CONFIG_NOTOPEN ? "YEL" : "");
- ast_data_add_str(data_span, "lbo", lbostr[s.lbo]);
-
- /* if this span doesn't match remove it. */
- if (!ast_data_search_match(search, data_span)) {
- ast_data_remove_node(data_root, data_span);
- }
- }
- close(ctl);
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief Callback used to generate the dahdi channels tree.
- * \param[in] search The search pattern tree.
- * \retval NULL on error.
- * \retval non-NULL The generated tree.
- */
-static int dahdi_channels_data_provider_get(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- struct dahdi_pvt *tmp;
- struct ast_data *data_channel;
-
- ast_mutex_lock(&iflock);
- for (tmp = iflist; tmp; tmp = tmp->next) {
- data_channel = ast_data_add_node(data_root, "channel");
- if (!data_channel) {
- continue;
- }
-
- ast_data_add_structure(dahdi_pvt, data_channel, tmp);
-
- /* if this channel doesn't match remove it. */
- if (!ast_data_search_match(search, data_channel)) {
- ast_data_remove_node(data_root, data_channel);
- }
- }
- ast_mutex_unlock(&iflock);
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief Callback used to generate the dahdi channels tree.
- * \param[in] search The search pattern tree.
- * \retval NULL on error.
- * \retval non-NULL The generated tree.
- */
-static int dahdi_version_data_provider_get(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- int pseudo_fd = -1;
- struct dahdi_versioninfo vi = {
- .version = "Unknown",
- .echo_canceller = "Unknown"
- };
-
- if ((pseudo_fd = open("/dev/dahdi/ctl", O_RDONLY)) < 0) {
- ast_log(LOG_ERROR, "Failed to open control file to get version.\n");
- return -1;
- }
-
- if (ioctl(pseudo_fd, DAHDI_GETVERSION, &vi)) {
- ast_log(LOG_ERROR, "Failed to get DAHDI version: %s\n", strerror(errno));
- }
-
- close(pseudo_fd);
-
- ast_data_add_str(data_root, "value", vi.version);
- ast_data_add_str(data_root, "echocanceller", vi.echo_canceller);
-
- return 0;
-}
-
-static const struct ast_data_handler dahdi_status_data_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = dahdi_status_data_provider_get
-};
-
-static const struct ast_data_handler dahdi_channels_data_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = dahdi_channels_data_provider_get
-};
-
-static const struct ast_data_handler dahdi_version_data_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = dahdi_version_data_provider_get
-};
-
-static const struct ast_data_entry dahdi_data_providers[] = {
- AST_DATA_ENTRY("asterisk/channel/dahdi/status", &dahdi_status_data_provider),
- AST_DATA_ENTRY("asterisk/channel/dahdi/channels", &dahdi_channels_data_provider),
- AST_DATA_ENTRY("asterisk/channel/dahdi/version", &dahdi_version_data_provider)
-};
-
-/*!
* \brief Load the module
*
* Module loading including tests for configuration or dependencies.
@@ -19608,8 +19377,6 @@ static int load_module(void)
#endif
ast_cli_register_multiple(dahdi_cli, ARRAY_LEN(dahdi_cli));
- /* register all the data providers */
- ast_data_register_multiple(dahdi_data_providers, ARRAY_LEN(dahdi_data_providers));
memset(round_robin, 0, sizeof(round_robin));
ast_manager_register_xml("DAHDITransfer", 0, action_transfer);
ast_manager_register_xml("DAHDIHangup", 0, action_transferhangup);
diff --git a/channels/chan_iax2.c b/channels/chan_iax2.c
index d15b55d72..5abb6c37f 100644
--- a/channels/chan_iax2.c
+++ b/channels/chan_iax2.c
@@ -106,7 +106,6 @@
#include "asterisk/timing.h"
#include "asterisk/taskprocessor.h"
#include "asterisk/test.h"
-#include "asterisk/data.h"
#include "asterisk/security_events.h"
#include "asterisk/stasis_endpoints.h"
#include "asterisk/bridge.h"
@@ -1950,19 +1949,6 @@ static int iax2_parse_allow_disallow(struct iax2_codec_pref *pref, iax2_format *
return res;
}
-static int iax2_data_add_codecs(struct ast_data *root, const char *node_name, iax2_format formats)
-{
- int res;
- struct ast_format_cap *cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
- if (!cap) {
- return -1;
- }
- iax2_format_compatibility_bitfield2cap(formats, cap);
- res = ast_data_add_codecs(root, node_name, cap);
- ao2_ref(cap, -1);
- return res;
-}
-
/*!
* \note The only member of the peer passed here guaranteed to be set is the name field
*/
@@ -13069,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);
@@ -14555,129 +14541,6 @@ static struct ast_cli_entry cli_iax2[] = {
#endif /* IAXTESTS */
};
-#ifdef TEST_FRAMEWORK
-AST_TEST_DEFINE(test_iax2_peers_get)
-{
- struct ast_data_query query = {
- .path = "/asterisk/channel/iax2/peers",
- .search = "peers/peer/name=test_peer_data_provider"
- };
- struct ast_data *node;
- struct iax2_peer *peer;
-
- switch (cmd) {
- case TEST_INIT:
- info->name = "iax2_peers_get_data_test";
- info->category = "/main/data/iax2/peers/";
- info->summary = "IAX2 peers data providers unit test";
- info->description =
- "Tests whether the IAX2 peers data provider implementation works as expected.";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
-
- /* build a test peer */
- peer = build_peer("test_peer_data_provider", NULL, NULL, 0);
- if (!peer) {
- return AST_TEST_FAIL;
- }
- peer->expiry= 1010;
- ao2_link(peers, peer);
-
- node = ast_data_get(&query);
- if (!node) {
- ao2_unlink(peers, peer);
- peer_unref(peer);
- return AST_TEST_FAIL;
- }
-
- /* check returned data node. */
- if (strcmp(ast_data_retrieve_string(node, "peer/name"), "test_peer_data_provider")) {
- ao2_unlink(peers, peer);
- peer_unref(peer);
- ast_data_free(node);
- return AST_TEST_FAIL;
- }
-
- if (ast_data_retrieve_int(node, "peer/expiry") != 1010) {
- ao2_unlink(peers, peer);
- peer_unref(peer);
- ast_data_free(node);
- return AST_TEST_FAIL;
- }
-
- /* release resources */
- ast_data_free(node);
-
- ao2_unlink(peers, peer);
- peer_unref(peer);
-
- return AST_TEST_PASS;
-}
-
-AST_TEST_DEFINE(test_iax2_users_get)
-{
- struct ast_data_query query = {
- .path = "/asterisk/channel/iax2/users",
- .search = "users/user/name=test_user_data_provider"
- };
- struct ast_data *node;
- struct iax2_user *user;
-
- switch (cmd) {
- case TEST_INIT:
- info->name = "iax2_users_get_data_test";
- info->category = "/main/data/iax2/users/";
- info->summary = "IAX2 users data providers unit test";
- info->description =
- "Tests whether the IAX2 users data provider implementation works as expected.";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
-
- user = build_user("test_user_data_provider", NULL, NULL, 0);
- if (!user) {
- ast_test_status_update(test, "Failed to build a test user\n");
- return AST_TEST_FAIL;
- }
- user->amaflags = 1010;
- ao2_link(users, user);
-
- node = ast_data_get(&query);
- if (!node) {
- ast_test_status_update(test, "The data query to find our test user failed\n");
- ao2_unlink(users, user);
- user_unref(user);
- return AST_TEST_FAIL;
- }
-
- if (strcmp(ast_data_retrieve_string(node, "user/name"), "test_user_data_provider")) {
- ast_test_status_update(test, "Our data results did not return the test user created in the previous step.\n");
- ao2_unlink(users, user);
- user_unref(user);
- ast_data_free(node);
- return AST_TEST_FAIL;
- }
-
- if (ast_data_retrieve_int(node, "user/amaflags/value") != 1010) {
- ast_test_status_update(test, "The amaflags field in our test user was '%d' not the expected value '1010'\n", ast_data_retrieve_int(node, "user/amaflags/value"));
- ao2_unlink(users, user);
- user_unref(user);
- ast_data_free(node);
- return AST_TEST_FAIL;
- }
-
- ast_data_free(node);
-
- ao2_unlink(users, user);
- user_unref(user);
-
- return AST_TEST_PASS;
-}
-#endif
-
static void cleanup_thread_list(void *head)
{
AST_LIST_HEAD(iax2_thread_list, iax2_thread);
@@ -14743,11 +14606,6 @@ static int __unload_module(void)
ast_manager_unregister( "IAXnetstats" );
ast_manager_unregister( "IAXregistry" );
ast_unregister_application(papp);
-#ifdef TEST_FRAMEWORK
- AST_TEST_UNREGISTER(test_iax2_peers_get);
- AST_TEST_UNREGISTER(test_iax2_users_get);
-#endif
- ast_data_unregister(NULL);
ast_cli_unregister_multiple(cli_iax2, ARRAY_LEN(cli_iax2));
ast_unregister_switch(&iax2_switch);
ast_channel_unregister(&iax2_tech);
@@ -14889,191 +14747,6 @@ container_fail:
return -1;
}
-
-#define DATA_EXPORT_IAX2_PEER(MEMBER) \
- MEMBER(iax2_peer, name, AST_DATA_STRING) \
- MEMBER(iax2_peer, username, AST_DATA_STRING) \
- MEMBER(iax2_peer, secret, AST_DATA_PASSWORD) \
- MEMBER(iax2_peer, dbsecret, AST_DATA_PASSWORD) \
- MEMBER(iax2_peer, outkey, AST_DATA_STRING) \
- MEMBER(iax2_peer, regexten, AST_DATA_STRING) \
- MEMBER(iax2_peer, context, AST_DATA_STRING) \
- MEMBER(iax2_peer, peercontext, AST_DATA_STRING) \
- MEMBER(iax2_peer, mailbox, AST_DATA_STRING) \
- MEMBER(iax2_peer, mohinterpret, AST_DATA_STRING) \
- MEMBER(iax2_peer, mohsuggest, AST_DATA_STRING) \
- MEMBER(iax2_peer, inkeys, AST_DATA_STRING) \
- MEMBER(iax2_peer, cid_num, AST_DATA_STRING) \
- MEMBER(iax2_peer, cid_name, AST_DATA_STRING) \
- MEMBER(iax2_peer, zonetag, AST_DATA_STRING) \
- MEMBER(iax2_peer, parkinglot, AST_DATA_STRING) \
- MEMBER(iax2_peer, expiry, AST_DATA_SECONDS) \
- MEMBER(iax2_peer, callno, AST_DATA_INTEGER) \
- MEMBER(iax2_peer, lastms, AST_DATA_MILLISECONDS) \
- MEMBER(iax2_peer, maxms, AST_DATA_MILLISECONDS) \
- MEMBER(iax2_peer, pokefreqok, AST_DATA_MILLISECONDS) \
- MEMBER(iax2_peer, pokefreqnotok, AST_DATA_MILLISECONDS) \
- MEMBER(iax2_peer, historicms, AST_DATA_INTEGER) \
- MEMBER(iax2_peer, smoothing, AST_DATA_BOOLEAN) \
- MEMBER(iax2_peer, maxcallno, AST_DATA_INTEGER)
-
-AST_DATA_STRUCTURE(iax2_peer, DATA_EXPORT_IAX2_PEER);
-
-static int peers_data_provider_get(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- struct ast_data *data_peer;
- struct iax2_peer *peer;
- struct ao2_iterator i;
- char status[20];
- struct ast_str *encmethods = ast_str_alloca(256);
-
- i = ao2_iterator_init(peers, 0);
- while ((peer = ao2_iterator_next(&i))) {
- data_peer = ast_data_add_node(data_root, "peer");
- if (!data_peer) {
- peer_unref(peer);
- continue;
- }
-
- ast_data_add_structure(iax2_peer, data_peer, peer);
-
- iax2_data_add_codecs(data_peer, "codecs", peer->capability);
-
- peer_status(peer, status, sizeof(status));
- ast_data_add_str(data_peer, "status", status);
-
- ast_data_add_str(data_peer, "host", ast_sockaddr_stringify_host(&peer->addr));
-
- ast_data_add_str(data_peer, "mask", ast_sockaddr_stringify_addr(&peer->mask));
-
- ast_data_add_int(data_peer, "port", ast_sockaddr_port(&peer->addr));
-
- ast_data_add_bool(data_peer, "trunk", ast_test_flag64(peer, IAX_TRUNK));
-
- ast_data_add_bool(data_peer, "dynamic", ast_test_flag64(peer, IAX_DYNAMIC));
-
- encmethods_to_str(peer->encmethods, &encmethods);
- ast_data_add_str(data_peer, "encryption", peer->encmethods ? ast_str_buffer(encmethods) : "no");
-
- peer_unref(peer);
-
- if (!ast_data_search_match(search, data_peer)) {
- ast_data_remove_node(data_root, data_peer);
- }
- }
- ao2_iterator_destroy(&i);
-
- return 0;
-}
-
-#define DATA_EXPORT_IAX2_USER(MEMBER) \
- MEMBER(iax2_user, name, AST_DATA_STRING) \
- MEMBER(iax2_user, dbsecret, AST_DATA_PASSWORD) \
- MEMBER(iax2_user, accountcode, AST_DATA_STRING) \
- MEMBER(iax2_user, mohinterpret, AST_DATA_STRING) \
- MEMBER(iax2_user, mohsuggest, AST_DATA_STRING) \
- MEMBER(iax2_user, inkeys, AST_DATA_STRING) \
- MEMBER(iax2_user, language, AST_DATA_STRING) \
- MEMBER(iax2_user, cid_num, AST_DATA_STRING) \
- MEMBER(iax2_user, cid_name, AST_DATA_STRING) \
- MEMBER(iax2_user, parkinglot, AST_DATA_STRING) \
- MEMBER(iax2_user, maxauthreq, AST_DATA_INTEGER) \
- MEMBER(iax2_user, curauthreq, AST_DATA_INTEGER)
-
-AST_DATA_STRUCTURE(iax2_user, DATA_EXPORT_IAX2_USER);
-
-static int users_data_provider_get(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- struct ast_data *data_user, *data_authmethods, *data_enum_node;
- struct iax2_user *user;
- struct ao2_iterator i;
- struct ast_str *auth;
- char *pstr = "";
-
- if (!(auth = ast_str_create(90))) {
- ast_log(LOG_ERROR, "Unable to create temporary string for storing 'secret'\n");
- return 0;
- }
-
- i = ao2_iterator_init(users, 0);
- for (; (user = ao2_iterator_next(&i)); user_unref(user)) {
- data_user = ast_data_add_node(data_root, "user");
- if (!data_user) {
- continue;
- }
-
- ast_data_add_structure(iax2_user, data_user, user);
-
- iax2_data_add_codecs(data_user, "codecs", user->capability);
-
- if (!ast_strlen_zero(user->secret)) {
- ast_str_set(&auth, 0, "%s", user->secret);
- } else if (!ast_strlen_zero(user->inkeys)) {
- ast_str_set(&auth, 0, "Key: %s", user->inkeys);
- } else {
- ast_str_set(&auth, 0, "no secret");
- }
- ast_data_add_password(data_user, "secret", ast_str_buffer(auth));
-
- ast_data_add_str(data_user, "context", user->contexts ? user->contexts->context : DEFAULT_CONTEXT);
-
- /* authmethods */
- data_authmethods = ast_data_add_node(data_user, "authmethods");
- if (!data_authmethods) {
- ast_data_remove_node(data_root, data_user);
- continue;
- }
- ast_data_add_bool(data_authmethods, "rsa", user->authmethods & IAX_AUTH_RSA);
- ast_data_add_bool(data_authmethods, "md5", user->authmethods & IAX_AUTH_MD5);
- ast_data_add_bool(data_authmethods, "plaintext", user->authmethods & IAX_AUTH_PLAINTEXT);
-
- /* amaflags */
- data_enum_node = ast_data_add_node(data_user, "amaflags");
- if (!data_enum_node) {
- ast_data_remove_node(data_root, data_user);
- continue;
- }
- ast_data_add_int(data_enum_node, "value", user->amaflags);
- ast_data_add_str(data_enum_node, "text", ast_channel_amaflags2string(user->amaflags));
-
- ast_data_add_bool(data_user, "access-control", ast_acl_list_is_empty(user->acl) ? 0 : 1);
-
- if (ast_test_flag64(user, IAX_CODEC_NOCAP)) {
- pstr = "REQ only";
- } else if (ast_test_flag64(user, IAX_CODEC_NOPREFS)) {
- pstr = "disabled";
- } else {
- pstr = ast_test_flag64(user, IAX_CODEC_USER_FIRST) ? "caller" : "host";
- }
- ast_data_add_str(data_user, "codec-preferences", pstr);
-
- if (!ast_data_search_match(search, data_user)) {
- ast_data_remove_node(data_root, data_user);
- }
- }
- ao2_iterator_destroy(&i);
-
- ast_free(auth);
- return 0;
-}
-
-static const struct ast_data_handler peers_data_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = peers_data_provider_get
-};
-
-static const struct ast_data_handler users_data_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = users_data_provider_get
-};
-
-static const struct ast_data_entry iax2_data_providers[] = {
- AST_DATA_ENTRY("asterisk/channel/iax2/peers", &peers_data_provider),
- AST_DATA_ENTRY("asterisk/channel/iax2/users", &users_data_provider),
-};
-
/*!
* \brief Load the module
*
@@ -15173,13 +14846,6 @@ static int load_module(void)
return AST_MODULE_LOAD_DECLINE;
}
-#ifdef TEST_FRAMEWORK
- AST_TEST_REGISTER(test_iax2_peers_get);
- AST_TEST_REGISTER(test_iax2_users_get);
-#endif
-
- /* Register AstData providers */
- ast_data_register_multiple(iax2_data_providers, ARRAY_LEN(iax2_data_providers));
ast_cli_register_multiple(cli_iax2, ARRAY_LEN(cli_iax2));
ast_register_application_xml(papp, iax2_prov_app);
diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 83dc77f38..51b5dab5c 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -64,6 +64,7 @@
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
+#include "asterisk/stream.h"
#include "pjsip/include/chan_pjsip.h"
#include "pjsip/include/dialplan_functions.h"
@@ -78,25 +79,22 @@ static unsigned int chan_idx;
static void chan_pjsip_pvt_dtor(void *obj)
{
- struct chan_pjsip_pvt *pvt = obj;
- int i;
-
- for (i = 0; i < SIP_MEDIA_SIZE; ++i) {
- ao2_cleanup(pvt->media[i]);
- pvt->media[i] = NULL;
- }
}
/* \brief Asterisk core interaction functions */
static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause);
+static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type,
+ struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids,
+ const struct ast_channel *requestor, const char *data, int *cause);
static int chan_pjsip_sendtext(struct ast_channel *ast, const char *text);
static int chan_pjsip_digit_begin(struct ast_channel *ast, char digit);
static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
static int chan_pjsip_call(struct ast_channel *ast, const char *dest, int timeout);
static int chan_pjsip_hangup(struct ast_channel *ast);
static int chan_pjsip_answer(struct ast_channel *ast);
-static struct ast_frame *chan_pjsip_read(struct ast_channel *ast);
+static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast);
static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *f);
+static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *f);
static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen);
static int chan_pjsip_transfer(struct ast_channel *ast, const char *target);
static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
@@ -109,16 +107,17 @@ struct ast_channel_tech chan_pjsip_tech = {
.type = channel_type,
.description = "PJSIP Channel Driver",
.requester = chan_pjsip_request,
+ .requester_with_stream_topology = chan_pjsip_request_with_stream_topology,
.send_text = chan_pjsip_sendtext,
.send_digit_begin = chan_pjsip_digit_begin,
.send_digit_end = chan_pjsip_digit_end,
.call = chan_pjsip_call,
.hangup = chan_pjsip_hangup,
.answer = chan_pjsip_answer,
- .read = chan_pjsip_read,
+ .read_stream = chan_pjsip_read_stream,
.write = chan_pjsip_write,
- .write_video = chan_pjsip_write,
- .exception = chan_pjsip_read,
+ .write_stream = chan_pjsip_write_stream,
+ .exception = chan_pjsip_read_stream,
.indicate = chan_pjsip_indicate,
.transfer = chan_pjsip_transfer,
.fixup = chan_pjsip_fixup,
@@ -159,11 +158,20 @@ static struct ast_sip_session_supplement chan_pjsip_ack_supplement = {
static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt;
struct ast_sip_endpoint *endpoint;
struct ast_datastore *datastore;
+ struct ast_sip_session_media *media;
+
+ if (!channel || !channel->session) {
+ return AST_RTP_GLUE_RESULT_FORBID;
+ }
- if (!channel || !channel->session || !(pvt = channel->pvt) || !pvt->media[SIP_MEDIA_AUDIO]->rtp) {
+ /* XXX Getting the first RTP instance for direct media related stuff seems just
+ * absolutely wrong. But the native RTP bridge knows no other method than single-stream
+ * for direct media. So this is the best we can do.
+ */
+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+ if (!media || !media->rtp) {
return AST_RTP_GLUE_RESULT_FORBID;
}
@@ -175,7 +183,7 @@ static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan
endpoint = channel->session->endpoint;
- *instance = pvt->media[SIP_MEDIA_AUDIO]->rtp;
+ *instance = media->rtp;
ao2_ref(*instance, +1);
ast_assert(endpoint != NULL);
@@ -194,16 +202,21 @@ static enum ast_rtp_glue_result chan_pjsip_get_rtp_peer(struct ast_channel *chan
static enum ast_rtp_glue_result chan_pjsip_get_vrtp_peer(struct ast_channel *chan, struct ast_rtp_instance **instance)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt = channel->pvt;
struct ast_sip_endpoint *endpoint;
+ struct ast_sip_session_media *media;
- if (!pvt || !channel->session || !pvt->media[SIP_MEDIA_VIDEO]->rtp) {
+ if (!channel || !channel->session) {
+ return AST_RTP_GLUE_RESULT_FORBID;
+ }
+
+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];
+ if (!media || !media->rtp) {
return AST_RTP_GLUE_RESULT_FORBID;
}
endpoint = channel->session->endpoint;
- *instance = pvt->media[SIP_MEDIA_VIDEO]->rtp;
+ *instance = media->rtp;
ao2_ref(*instance, +1);
ast_assert(endpoint != NULL);
@@ -265,18 +278,43 @@ static int direct_media_mitigate_glare(struct ast_sip_session *session)
return 0;
}
+/*! \brief Helper function to find the position for RTCP */
+static int rtp_find_rtcp_fd_position(struct ast_sip_session *session, struct ast_rtp_instance *rtp)
+{
+ int index;
+
+ for (index = 0; index < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++index) {
+ struct ast_sip_session_media_read_callback_state *callback_state =
+ AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, index);
+
+ if (callback_state->fd != ast_rtp_instance_fd(rtp, 1)) {
+ continue;
+ }
+
+ return index;
+ }
+
+ return -1;
+}
+
/*!
* \pre chan is locked
*/
static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instance *rtp,
- struct ast_sip_session_media *media, int rtcp_fd)
+ struct ast_sip_session_media *media, struct ast_sip_session *session)
{
- int changed = 0;
+ int changed = 0, position = -1;
+
+ if (media->rtp) {
+ position = rtp_find_rtcp_fd_position(session, media->rtp);
+ }
if (rtp) {
changed = ast_rtp_instance_get_and_cmp_remote_address(rtp, &media->direct_media_addr);
if (media->rtp) {
- ast_channel_set_fd(chan, rtcp_fd, -1);
+ if (position != -1) {
+ ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, -1);
+ }
ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 0);
}
} else if (!ast_sockaddr_isnull(&media->direct_media_addr)){
@@ -284,7 +322,9 @@ static int check_for_rtp_changes(struct ast_channel *chan, struct ast_rtp_instan
changed = 1;
if (media->rtp) {
ast_rtp_instance_set_prop(media->rtp, AST_RTP_PROPERTY_RTCP, 1);
- ast_channel_set_fd(chan, rtcp_fd, ast_rtp_instance_fd(media->rtp, 1));
+ if (position != -1) {
+ ast_channel_set_fd(chan, position + AST_EXTENDED_FDS, ast_rtp_instance_fd(media->rtp, 1));
+ }
}
}
@@ -333,22 +373,27 @@ static int send_direct_media_request(void *data)
{
struct rtp_direct_media_data *cdata = data;
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(cdata->chan);
- struct chan_pjsip_pvt *pvt = channel->pvt;
+ struct ast_sip_session *session;
int changed = 0;
int res = 0;
+ /* XXX In an ideal world each media stream would be direct, but for now preserve behavior
+ * and connect only the default media sessions for audio and video.
+ */
+
/* The channel needs to be locked when checking for RTP changes.
* Otherwise, we could end up destroying an underlying RTCP structure
* at the same time that the channel thread is attempting to read RTCP
*/
ast_channel_lock(cdata->chan);
- if (pvt->media[SIP_MEDIA_AUDIO]) {
+ session = channel->session;
+ if (session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]) {
changed |= check_for_rtp_changes(
- cdata->chan, cdata->rtp, pvt->media[SIP_MEDIA_AUDIO], 1);
+ cdata->chan, cdata->rtp, session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO], session);
}
- if (pvt->media[SIP_MEDIA_VIDEO]) {
+ if (session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO]) {
changed |= check_for_rtp_changes(
- cdata->chan, cdata->vrtp, pvt->media[SIP_MEDIA_VIDEO], 3);
+ cdata->chan, cdata->vrtp, session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO], session);
}
ast_channel_unlock(cdata->chan);
@@ -368,7 +413,7 @@ static int send_direct_media_request(void *data)
if (changed) {
ast_debug(4, "RTP changed on %s; initiating direct media update\n", ast_channel_name(cdata->chan));
res = ast_sip_session_refresh(cdata->session, NULL, NULL, NULL,
- cdata->session->endpoint->media.direct_media.method, 1);
+ cdata->session->endpoint->media.direct_media.method, 1, NULL);
}
ao2_ref(cdata, -1);
@@ -420,14 +465,53 @@ static struct ast_rtp_glue chan_pjsip_rtp_glue = {
.update_peer = chan_pjsip_set_rtp_peer,
};
-static void set_channel_on_rtp_instance(struct chan_pjsip_pvt *pvt, const char *channel_id)
+static void set_channel_on_rtp_instance(const struct ast_sip_session *session,
+ const char *channel_id)
{
- if (pvt->media[SIP_MEDIA_AUDIO] && pvt->media[SIP_MEDIA_AUDIO]->rtp) {
- ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_AUDIO]->rtp, channel_id);
+ int i;
+
+ for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->sessions); ++i) {
+ struct ast_sip_session_media *session_media;
+
+ session_media = AST_VECTOR_GET(&session->active_media_state->sessions, i);
+ if (!session_media || !session_media->rtp) {
+ continue;
+ }
+
+ ast_rtp_instance_set_channel_id(session_media->rtp, channel_id);
}
- if (pvt->media[SIP_MEDIA_VIDEO] && pvt->media[SIP_MEDIA_VIDEO]->rtp) {
- ast_rtp_instance_set_channel_id(pvt->media[SIP_MEDIA_VIDEO]->rtp, channel_id);
+}
+
+/*!
+ * \brief Determine if a topology is compatible with format capabilities
+ *
+ * This will return true if ANY formats in the topology are compatible with the format
+ * capabilities.
+ *
+ * XXX When supporting true multistream, we will need to be sure to mark which streams from
+ * top1 are compatible with which streams from top2. Then the ones that are not compatible
+ * will need to be marked as "removed" so that they are negotiated as expected.
+ *
+ * \param top Topology
+ * \param cap Format capabilities
+ * \retval 1 The topology has at least one compatible format
+ * \retval 0 The topology has no compatible formats or an error occurred.
+ */
+static int compatible_formats_exist(struct ast_stream_topology *top, struct ast_format_cap *cap)
+{
+ struct ast_format_cap *cap_from_top;
+ int res;
+
+ cap_from_top = ast_format_cap_from_stream_topology(top);
+
+ if (!cap_from_top) {
+ return 0;
}
+
+ res = ast_format_cap_iscompatible(cap_from_top, cap);
+ ao2_ref(cap_from_top, -1);
+
+ return res;
}
/*! \brief Function called to create a new PJSIP Asterisk channel */
@@ -438,12 +522,9 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s
RAII_VAR(struct chan_pjsip_pvt *, pvt, NULL, ao2_cleanup);
struct ast_sip_channel_pvt *channel;
struct ast_variable *var;
+ struct ast_stream_topology *topology;
- if (!(pvt = ao2_alloc(sizeof(*pvt), chan_pjsip_pvt_dtor))) {
- return NULL;
- }
- caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
- if (!caps) {
+ if (!(pvt = ao2_alloc_options(sizeof(*pvt), chan_pjsip_pvt_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK))) {
return NULL;
}
@@ -457,31 +538,46 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s
ast_sorcery_object_get_id(session->endpoint),
(unsigned) ast_atomic_fetchadd_int((int *) &chan_idx, +1));
if (!chan) {
- ao2_ref(caps, -1);
return NULL;
}
ast_channel_tech_set(chan, &chan_pjsip_tech);
if (!(channel = ast_sip_channel_pvt_alloc(pvt, session))) {
- ao2_ref(caps, -1);
ast_channel_unlock(chan);
ast_hangup(chan);
return NULL;
}
- ast_channel_stage_snapshot(chan);
-
ast_channel_tech_pvt_set(chan, channel);
- if (!ast_format_cap_count(session->req_caps) ||
- !ast_format_cap_iscompatible(session->req_caps, session->endpoint->media.codecs)) {
+ if (!ast_stream_topology_get_count(session->pending_media_state->topology) ||
+ !compatible_formats_exist(session->pending_media_state->topology, session->endpoint->media.codecs)) {
+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!caps) {
+ ast_channel_unlock(chan);
+ ast_hangup(chan);
+ return NULL;
+ }
ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);
+ topology = ast_stream_topology_clone(session->endpoint->media.topology);
} else {
- ast_format_cap_append_from_cap(caps, session->req_caps, AST_MEDIA_TYPE_UNKNOWN);
+ caps = ast_format_cap_from_stream_topology(session->pending_media_state->topology);
+ topology = ast_stream_topology_clone(session->pending_media_state->topology);
}
+ if (!topology || !caps) {
+ ao2_cleanup(caps);
+ ast_stream_topology_free(topology);
+ ast_channel_unlock(chan);
+ ast_hangup(chan);
+ return NULL;
+ }
+
+ ast_channel_stage_snapshot(chan);
+
ast_channel_nativeformats_set(chan, caps);
+ ast_channel_set_stream_topology(chan, topology);
if (!ast_format_cap_empty(caps)) {
struct ast_format *fmt;
@@ -538,12 +634,7 @@ static struct ast_channel *chan_pjsip_new(struct ast_sip_session *session, int s
ast_channel_stage_snapshot_done(chan);
ast_channel_unlock(chan);
- /* If res_pjsip_session is ever updated to create/destroy ast_sip_session_media
- * during a call such as if multiple same-type stream support is introduced,
- * these will need to be recaptured as well */
- pvt->media[SIP_MEDIA_AUDIO] = ao2_find(session->media, "audio", OBJ_KEY);
- pvt->media[SIP_MEDIA_VIDEO] = ao2_find(session->media, "video", OBJ_KEY);
- set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(chan));
+ set_channel_on_rtp_instance(session, ast_channel_uniqueid(chan));
return chan;
}
@@ -682,49 +773,30 @@ static struct ast_frame *chan_pjsip_cng_tone_detected(struct ast_sip_session *se
*
* \note The channel is already locked.
*/
-static struct ast_frame *chan_pjsip_read(struct ast_channel *ast)
+static struct ast_frame *chan_pjsip_read_stream(struct ast_channel *ast)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct ast_sip_session *session;
- struct chan_pjsip_pvt *pvt = channel->pvt;
+ struct ast_sip_session *session = channel->session;
+ struct ast_sip_session_media_read_callback_state *callback_state;
struct ast_frame *f;
- struct ast_sip_session_media *media = NULL;
- int rtcp = 0;
- int fdno = ast_channel_fdno(ast);
+ int fdno = ast_channel_fdno(ast) - AST_EXTENDED_FDS;
- switch (fdno) {
- case 0:
- media = pvt->media[SIP_MEDIA_AUDIO];
- break;
- case 1:
- media = pvt->media[SIP_MEDIA_AUDIO];
- rtcp = 1;
- break;
- case 2:
- media = pvt->media[SIP_MEDIA_VIDEO];
- break;
- case 3:
- media = pvt->media[SIP_MEDIA_VIDEO];
- rtcp = 1;
- break;
- }
-
- if (!media || !media->rtp) {
+ if (fdno >= AST_VECTOR_SIZE(&session->active_media_state->read_callbacks)) {
return &ast_null_frame;
}
- if (!(f = ast_rtp_instance_read(media->rtp, rtcp))) {
+ callback_state = AST_VECTOR_GET_ADDR(&session->active_media_state->read_callbacks, fdno);
+ f = callback_state->read_callback(session, callback_state->session);
+
+ if (!f) {
return f;
}
- ast_rtp_instance_set_last_rx(media->rtp, time(NULL));
-
- if (f->frametype != AST_FRAME_VOICE) {
+ if (f->frametype != AST_FRAME_VOICE ||
+ callback_state->session != session->active_media_state->default_session[callback_state->session->type]) {
return f;
}
- session = channel->session;
-
if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), f->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
ast_debug(1, "Oooh, got a frame with format of %s on channel '%s' when it has not been negotiated\n",
ast_format_get_name(f->subclass.format), ast_channel_name(ast));
@@ -794,22 +866,31 @@ static struct ast_frame *chan_pjsip_read(struct ast_channel *ast)
return f;
}
-/*! \brief Function called by core to write frames */
-static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
+static int chan_pjsip_write_stream(struct ast_channel *ast, int stream_num, struct ast_frame *frame)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt = channel->pvt;
- struct ast_sip_session_media *media;
+ struct ast_sip_session *session = channel->session;
+ struct ast_sip_session_media *media = NULL;
int res = 0;
+ /* The core provides a guarantee that the stream will exist when we are called if stream_num is provided */
+ if (stream_num >= 0) {
+ /* What is not guaranteed is that a media session will exist */
+ if (stream_num < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions)) {
+ media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, stream_num);
+ }
+ }
+
switch (frame->frametype) {
case AST_FRAME_VOICE:
- media = pvt->media[SIP_MEDIA_AUDIO];
-
if (!media) {
return 0;
- }
- if (ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
+ } else if (media->type != AST_MEDIA_TYPE_AUDIO) {
+ ast_debug(3, "Channel %s stream %d is of type '%s', not audio!\n",
+ ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
+ return 0;
+ } else if (media == channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO] &&
+ ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), frame->subclass.format) == AST_FORMAT_CMP_NOT_EQUAL) {
struct ast_str *cap_buf = ast_str_alloca(AST_FORMAT_CAP_NAMES_LEN);
struct ast_str *write_transpath = ast_str_alloca(256);
struct ast_str *read_transpath = ast_str_alloca(256);
@@ -826,17 +907,32 @@ static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
ast_format_get_name(ast_channel_rawwriteformat(ast)),
ast_translate_path_to_str(ast_channel_writetrans(ast), &write_transpath));
return 0;
- }
- if (media->rtp) {
- res = ast_rtp_instance_write(media->rtp, frame);
+ } else if (media->write_callback) {
+ res = media->write_callback(session, media, frame);
+
}
break;
case AST_FRAME_VIDEO:
- if ((media = pvt->media[SIP_MEDIA_VIDEO]) && media->rtp) {
- res = ast_rtp_instance_write(media->rtp, frame);
+ if (!media) {
+ return 0;
+ } else if (media->type != AST_MEDIA_TYPE_VIDEO) {
+ ast_debug(3, "Channel %s stream %d is of type '%s', not video!\n",
+ ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
+ return 0;
+ } else if (media->write_callback) {
+ res = media->write_callback(session, media, frame);
}
break;
case AST_FRAME_MODEM:
+ if (!media) {
+ return 0;
+ } else if (media->type != AST_MEDIA_TYPE_IMAGE) {
+ ast_debug(3, "Channel %s stream %d is of type '%s', not image!\n",
+ ast_channel_name(ast), stream_num, ast_codec_media_type2str(media->type));
+ return 0;
+ } else if (media->write_callback) {
+ res = media->write_callback(session, media, frame);
+ }
break;
default:
ast_log(LOG_WARNING, "Can't send %u type frames with PJSIP\n", frame->frametype);
@@ -846,11 +942,15 @@ static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
return res;
}
+static int chan_pjsip_write(struct ast_channel *ast, struct ast_frame *frame)
+{
+ return chan_pjsip_write_stream(ast, -1, frame);
+}
+
/*! \brief Function called by core to change the underlying owner channel */
static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(newchan);
- struct chan_pjsip_pvt *pvt = channel->pvt;
if (channel->session->channel != oldchan) {
return -1;
@@ -863,7 +963,7 @@ static int chan_pjsip_fixup(struct ast_channel *oldchan, struct ast_channel *new
*/
channel->session->channel = newchan;
- set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(newchan));
+ set_channel_on_rtp_instance(channel->session, ast_channel_uniqueid(newchan));
return 0;
}
@@ -1270,15 +1370,14 @@ static int update_connected_line_information(void *data)
int generate_new_sdp;
method = session->endpoint->id.refresh_method;
- if (session->inv_session->invite_tsx
- && (session->inv_session->options & PJSIP_INV_SUPPORT_UPDATE)) {
+ if (session->inv_session->options & PJSIP_INV_SUPPORT_UPDATE) {
method = AST_SIP_SESSION_REFRESH_METHOD_UPDATE;
}
/* Only the INVITE method actually needs SDP, UPDATE can do without */
generate_new_sdp = (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE);
- ast_sip_session_refresh(session, NULL, NULL, NULL, method, generate_new_sdp);
+ ast_sip_session_refresh(session, NULL, NULL, NULL, method, generate_new_sdp, NULL);
}
} else if (session->endpoint->id.rpid_immediate
&& session->inv_session->state != PJSIP_INV_STATE_DISCONNECTED
@@ -1309,21 +1408,18 @@ static int update_connected_line_information(void *data)
}
/*! \brief Callback which changes the value of locally held on the media stream */
-static int local_hold_set_state(void *obj, void *arg, int flags)
+static void local_hold_set_state(struct ast_sip_session_media *session_media, unsigned int held)
{
- struct ast_sip_session_media *session_media = obj;
- unsigned int *held = arg;
-
- session_media->locally_held = *held;
-
- return 0;
+ if (session_media) {
+ session_media->locally_held = held;
+ }
}
/*! \brief Update local hold state and send a re-INVITE with the new SDP */
static int remote_send_hold_refresh(struct ast_sip_session *session, unsigned int held)
{
- ao2_callback(session->media, OBJ_NODATA, local_hold_set_state, &held);
- ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);
+ AST_VECTOR_CALLBACK_VOID(&session->active_media_state->sessions, local_hold_set_state, held);
+ ast_sip_session_refresh(session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, NULL);
ao2_ref(session, -1);
return 0;
@@ -1341,16 +1437,103 @@ static int remote_send_unhold(void *data)
return remote_send_hold_refresh(data, 0);
}
+struct topology_change_refresh_data {
+ struct ast_sip_session *session;
+ struct ast_sip_session_media_state *media_state;
+};
+
+static void topology_change_refresh_data_free(struct topology_change_refresh_data *refresh_data)
+{
+ ao2_cleanup(refresh_data->session);
+
+ ast_sip_session_media_state_free(refresh_data->media_state);
+ ast_free(refresh_data);
+}
+
+static struct topology_change_refresh_data *topology_change_refresh_data_alloc(
+ struct ast_sip_session *session, const struct ast_stream_topology *topology)
+{
+ struct topology_change_refresh_data *refresh_data;
+
+ refresh_data = ast_calloc(1, sizeof(*refresh_data));
+ if (!refresh_data) {
+ return NULL;
+ }
+
+ refresh_data->session = ao2_bump(session);
+ refresh_data->media_state = ast_sip_session_media_state_alloc();
+ if (!refresh_data->media_state) {
+ topology_change_refresh_data_free(refresh_data);
+ return NULL;
+ }
+ refresh_data->media_state->topology = ast_stream_topology_clone(topology);
+ if (!refresh_data->media_state->topology) {
+ topology_change_refresh_data_free(refresh_data);
+ return NULL;
+ }
+
+ return refresh_data;
+}
+
+static int on_topology_change_response(struct ast_sip_session *session, pjsip_rx_data *rdata)
+{
+ if (rdata->msg_info.msg->line.status.code == 200) {
+ /* The topology was changed to something new so give notice to what requested
+ * it so it queries the channel and updates accordingly.
+ */
+ if (session->channel) {
+ ast_queue_control(session->channel, AST_CONTROL_STREAM_TOPOLOGY_CHANGED);
+ }
+ } else if (rdata->msg_info.msg->line.status.code != 100) {
+ /* The topology change failed, so drop the current pending media state */
+ ast_sip_session_media_state_reset(session->pending_media_state);
+ }
+
+ return 0;
+}
+
+static int send_topology_change_refresh(void *data)
+{
+ struct topology_change_refresh_data *refresh_data = data;
+ int ret;
+
+ ret = ast_sip_session_refresh(refresh_data->session, NULL, NULL, on_topology_change_response,
+ AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, refresh_data->media_state);
+ refresh_data->media_state = NULL;
+ topology_change_refresh_data_free(refresh_data);
+
+ return ret;
+}
+
+static int handle_topology_request_change(struct ast_sip_session *session,
+ const struct ast_stream_topology *proposed)
+{
+ struct topology_change_refresh_data *refresh_data;
+ int res;
+
+ refresh_data = topology_change_refresh_data_alloc(session, proposed);
+ if (!refresh_data) {
+ return -1;
+ }
+
+ res = ast_sip_push_task(session->serializer, send_topology_change_refresh, refresh_data);
+ if (res) {
+ topology_change_refresh_data_free(refresh_data);
+ }
+ return res;
+}
+
/*! \brief Function called by core to ask the channel to indicate some sort of condition */
static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const void *data, size_t datalen)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt = channel->pvt;
struct ast_sip_session_media *media;
int response_code = 0;
int res = 0;
char *device_buf;
size_t device_buf_size;
+ int i;
+ const struct ast_stream_topology *topology;
switch (condition) {
case AST_CONTROL_RINGING:
@@ -1403,39 +1586,50 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
ast_devstate_changed(AST_DEVICE_UNKNOWN, AST_DEVSTATE_CACHABLE, "PJSIP/%s", ast_sorcery_object_get_id(channel->session->endpoint));
break;
case AST_CONTROL_VIDUPDATE:
- media = pvt->media[SIP_MEDIA_VIDEO];
- if (media && media->rtp) {
- /* 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) {
- /* 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 */
- struct ast_frame fr;
- fr.frametype = AST_FRAME_CONTROL;
- fr.subclass.integer = AST_CONTROL_VIDUPDATE;
- res = ast_rtp_instance_write(media->rtp, &fr);
- } else {
- ao2_ref(channel->session, +1);
-#ifdef HAVE_PJSIP_INV_SESSION_REF
- if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) {
- ast_log(LOG_ERROR, "Can't increase the session reference counter\n");
- ao2_cleanup(channel->session);
+ for (i = 0; i < AST_VECTOR_SIZE(&channel->session->active_media_state->sessions); ++i) {
+ media = AST_VECTOR_GET(&channel->session->active_media_state->sessions, i);
+ if (!media || media->type != AST_MEDIA_TYPE_VIDEO) {
+ continue;
+ }
+ if (media->rtp) {
+ /* 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 ||
+ ast_format_cap_iscompatible_format(ast_channel_nativeformats(ast), ast_format_vp9) != 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 */
+ struct ast_frame fr;
+ fr.frametype = AST_FRAME_CONTROL;
+ fr.subclass.integer = AST_CONTROL_VIDUPDATE;
+ res = ast_rtp_instance_write(media->rtp, &fr);
} else {
-#endif
- if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) {
+ ao2_ref(channel->session, +1);
+#ifdef HAVE_PJSIP_INV_SESSION_REF
+ if (pjsip_inv_add_ref(channel->session->inv_session) != PJ_SUCCESS) {
+ ast_log(LOG_ERROR, "Can't increase the session reference counter\n");
ao2_cleanup(channel->session);
- }
+ } else {
+#endif
+ if (ast_sip_push_task(channel->session->serializer, transmit_info_with_vidupdate, channel->session)) {
+ ao2_cleanup(channel->session);
+ }
#ifdef HAVE_PJSIP_INV_SESSION_REF
- }
+ }
#endif
+ }
+ ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success");
+ } else {
+ ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure");
+ res = -1;
}
- ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Success");
- } else {
- ast_test_suite_event_notify("AST_CONTROL_VIDUPDATE", "Result: Failure");
- res = -1;
}
+ /* XXX If there were no video streams, then this should set
+ * res to -1
+ */
break;
case AST_CONTROL_CONNECTED_LINE:
ao2_ref(channel->session, +1);
@@ -1531,6 +1725,10 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
}
break;
+ case AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE:
+ topology = data;
+ res = handle_topology_request_change(channel->session, topology);
+ break;
case -1:
res = -1;
break;
@@ -1744,10 +1942,11 @@ static int chan_pjsip_transfer(struct ast_channel *chan, const char *target)
static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt = channel->pvt;
- struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO];
+ struct ast_sip_session_media *media;
int res = 0;
+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+
switch (channel->session->endpoint->dtmf) {
case AST_SIP_DTMF_RFC_4733:
if (!media || !media->rtp) {
@@ -1755,14 +1954,20 @@ static int chan_pjsip_digit_begin(struct ast_channel *chan, char digit)
}
ast_rtp_instance_dtmf_begin(media->rtp, digit);
- break;
+ break;
case AST_SIP_DTMF_AUTO:
- if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
- return -1;
- }
+ if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
+ return -1;
+ }
- ast_rtp_instance_dtmf_begin(media->rtp, digit);
- break;
+ ast_rtp_instance_dtmf_begin(media->rtp, digit);
+ break;
+ case AST_SIP_DTMF_AUTO_INFO:
+ if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_NONE)) {
+ return -1;
+ }
+ ast_rtp_instance_dtmf_begin(media->rtp, digit);
+ break;
case AST_SIP_DTMF_NONE:
break;
case AST_SIP_DTMF_INBAND:
@@ -1858,11 +2063,26 @@ failure:
static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt = channel->pvt;
- struct ast_sip_session_media *media = pvt->media[SIP_MEDIA_AUDIO];
+ struct ast_sip_session_media *media;
int res = 0;
+ media = channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+
switch (channel->session->endpoint->dtmf) {
+ case AST_SIP_DTMF_AUTO_INFO:
+ {
+ if (!media || !media->rtp) {
+ return -1;
+ }
+ if (ast_rtp_instance_dtmf_mode_get(media->rtp) != AST_RTP_DTMF_MODE_NONE) {
+ ast_debug(3, "Told to send end of digit on Auto-Info channel %s RFC4733 negotiated so using it.\n", ast_channel_name(ast));
+ ast_rtp_instance_dtmf_end_with_duration(media->rtp, digit, duration);
+ break;
+ }
+ /* If RFC_4733 was not negotiated, fail through to the DTMF_INFO processing */
+ ast_debug(3, "Told to send end of digit on Auto-Info channel %s RFC4733 NOT negotiated using INFO instead.\n", ast_channel_name(ast));
+ }
+
case AST_SIP_DTMF_INFO:
{
struct info_dtmf_data *dtmf_data = info_dtmf_data_alloc(channel->session, digit, duration);
@@ -1895,14 +2115,15 @@ static int chan_pjsip_digit_end(struct ast_channel *ast, char digit, unsigned in
}
ast_rtp_instance_dtmf_end_with_duration(media->rtp, digit, duration);
- break;
- case AST_SIP_DTMF_AUTO:
- if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
- return -1;
- }
+ break;
+ case AST_SIP_DTMF_AUTO:
+ if (!media || !media->rtp || (ast_rtp_instance_dtmf_mode_get(media->rtp) == AST_RTP_DTMF_MODE_INBAND)) {
+ return -1;
+ }
+
+ ast_rtp_instance_dtmf_end_with_duration(media->rtp, digit, duration);
+ break;
- ast_rtp_instance_dtmf_end_with_duration(media->rtp, digit, duration);
- break;
case AST_SIP_DTMF_NONE:
break;
@@ -1943,7 +2164,6 @@ static int call(void *data)
{
struct ast_sip_channel_pvt *channel = data;
struct ast_sip_session *session = channel->session;
- struct chan_pjsip_pvt *pvt = channel->pvt;
pjsip_tx_data *tdata;
int res = ast_sip_session_create_invite(session, &tdata);
@@ -1952,7 +2172,7 @@ static int call(void *data)
ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0);
ast_queue_hangup(session->channel);
} else {
- set_channel_on_rtp_instance(pvt, ast_channel_uniqueid(session->channel));
+ set_channel_on_rtp_instance(session, ast_channel_uniqueid(session->channel));
update_initial_connected_line(session);
ast_sip_session_send_request(session, tdata);
}
@@ -2050,10 +2270,10 @@ static struct hangup_data *hangup_data_alloc(int cause, struct ast_channel *chan
}
/*! \brief Clear a channel from a session along with its PVT */
-static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast, struct chan_pjsip_pvt *pvt)
+static void clear_session_and_channel(struct ast_sip_session *session, struct ast_channel *ast)
{
session->channel = NULL;
- set_channel_on_rtp_instance(pvt, "");
+ set_channel_on_rtp_instance(session, "");
ast_channel_tech_pvt_set(ast, NULL);
}
@@ -2062,7 +2282,6 @@ static int hangup(void *data)
struct hangup_data *h_data = data;
struct ast_channel *ast = h_data->chan;
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt = channel->pvt;
struct ast_sip_session *session = channel->session;
int cause = h_data->cause;
@@ -2072,7 +2291,7 @@ static int hangup(void *data)
* afterwards.
*/
ast_sip_session_terminate(ao2_bump(session), cause);
- clear_session_and_channel(session, ast, pvt);
+ clear_session_and_channel(session, ast);
ao2_cleanup(session);
ao2_cleanup(channel);
ao2_cleanup(h_data);
@@ -2083,7 +2302,6 @@ static int hangup(void *data)
static int chan_pjsip_hangup(struct ast_channel *ast)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(ast);
- struct chan_pjsip_pvt *pvt;
int cause;
struct hangup_data *h_data;
@@ -2091,7 +2309,6 @@ static int chan_pjsip_hangup(struct ast_channel *ast)
return -1;
}
- pvt = channel->pvt;
cause = hangup_cause2sip(ast_channel_hangupcause(channel->session->channel));
h_data = hangup_data_alloc(cause, ast);
@@ -2110,7 +2327,7 @@ failure:
/* Go ahead and do our cleanup of the session and channel even if we're not going
* to be able to send our SIP request/response
*/
- clear_session_and_channel(channel->session, ast, pvt);
+ clear_session_and_channel(channel->session, ast);
ao2_cleanup(channel);
ao2_cleanup(h_data);
@@ -2119,7 +2336,7 @@ failure:
struct request_data {
struct ast_sip_session *session;
- struct ast_format_cap *caps;
+ struct ast_stream_topology *topology;
const char *dest;
int cause;
};
@@ -2193,7 +2410,7 @@ static int request(void *obj)
}
}
- if (!(session = ast_sip_session_create_outgoing(endpoint, NULL, args.aor, request_user, req_data->caps))) {
+ if (!(session = ast_sip_session_create_outgoing(endpoint, NULL, args.aor, request_user, req_data->topology))) {
ast_log(LOG_ERROR, "Failed to create outgoing session to endpoint '%s'\n", endpoint_name);
req_data->cause = AST_CAUSE_NO_ROUTE_DESTINATION;
return -1;
@@ -2205,12 +2422,12 @@ static int request(void *obj)
}
/*! \brief Function called by core to create a new outgoing PJSIP session */
-static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
+static struct ast_channel *chan_pjsip_request_with_stream_topology(const char *type, struct ast_stream_topology *topology, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
{
struct request_data req_data;
RAII_VAR(struct ast_sip_session *, session, NULL, ao2_cleanup);
- req_data.caps = cap;
+ req_data.topology = topology;
req_data.dest = data;
if (ast_sip_push_task_synchronous(NULL, request, &req_data)) {
@@ -2228,6 +2445,23 @@ static struct ast_channel *chan_pjsip_request(const char *type, struct ast_forma
return session->channel;
}
+static struct ast_channel *chan_pjsip_request(const char *type, struct ast_format_cap *cap, const struct ast_assigned_ids *assignedids, const struct ast_channel *requestor, const char *data, int *cause)
+{
+ struct ast_stream_topology *topology;
+ struct ast_channel *chan;
+
+ topology = ast_stream_topology_create_from_format_cap(cap);
+ if (!topology) {
+ return NULL;
+ }
+
+ chan = chan_pjsip_request_with_stream_topology(type, topology, assignedids, requestor, data, cause);
+
+ ast_stream_topology_free(topology);
+
+ return chan;
+}
+
struct sendtext_data {
struct ast_sip_session *session;
char text[0];
diff --git a/channels/chan_sip.c b/channels/chan_sip.c
index aaeb01e13..f2daf2b8f 100644
--- a/channels/chan_sip.c
+++ b/channels/chan_sip.c
@@ -263,7 +263,6 @@
#include "asterisk/threadstorage.h"
#include "asterisk/translate.h"
#include "asterisk/ast_version.h"
-#include "asterisk/data.h"
#include "asterisk/aoc.h"
#include "asterisk/message.h"
#include "sip/include/sip.h"
@@ -33278,17 +33277,17 @@ static int reload_config(enum channelreloadreason reason)
/* If TCP is running on a different IP than UDP, then add it too */
if (!ast_sockaddr_isnull(&sip_tcp_desc.local_address) &&
- !ast_sockaddr_cmp(&bindaddr, &sip_tcp_desc.local_address)) {
+ ast_sockaddr_cmp_addr(&bindaddr, &sip_tcp_desc.local_address)) {
add_sip_domain(ast_sockaddr_stringify_addr(&sip_tcp_desc.local_address),
SIP_DOMAIN_AUTO, NULL);
}
/* If TLS is running on a different IP than UDP and TCP, then add that too */
if (!ast_sockaddr_isnull(&sip_tls_desc.local_address) &&
- !ast_sockaddr_cmp(&bindaddr, &sip_tls_desc.local_address) &&
- !ast_sockaddr_cmp(&sip_tcp_desc.local_address,
+ ast_sockaddr_cmp_addr(&bindaddr, &sip_tls_desc.local_address) &&
+ ast_sockaddr_cmp_addr(&sip_tcp_desc.local_address,
&sip_tls_desc.local_address)) {
- add_sip_domain(ast_sockaddr_stringify_addr(&sip_tcp_desc.local_address),
+ add_sip_domain(ast_sockaddr_stringify_addr(&sip_tls_desc.local_address),
SIP_DOMAIN_AUTO, NULL);
}
@@ -34530,75 +34529,6 @@ AST_TEST_DEFINE(test_sip_mwi_subscribe_parse)
return res;
}
-AST_TEST_DEFINE(test_sip_peers_get)
-{
- struct sip_peer *peer;
- struct ast_data *node;
- struct ast_data_query query = {
- .path = "/asterisk/channel/sip/peers",
- .search = "peers/peer/name=test_peer_data_provider"
- };
-
- switch (cmd) {
- case TEST_INIT:
- info->name = "sip_peers_get_data_test";
- info->category = "/main/data/sip/peers/";
- info->summary = "SIP peers data providers unit test";
- info->description =
- "Tests whether the SIP peers data provider implementation works as expected.";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
-
- /* Create the peer that we will retrieve. */
- peer = build_peer("test_peer_data_provider", NULL, NULL, 0, 0);
- if (!peer) {
- return AST_TEST_FAIL;
- }
- peer->type = SIP_TYPE_USER;
- peer->call_limit = 10;
- ao2_link(peers, peer);
-
- /* retrieve the chan_sip/peers tree and check the created peer. */
- node = ast_data_get(&query);
- if (!node) {
- ao2_unlink(peers, peer);
- ao2_ref(peer, -1);
- return AST_TEST_FAIL;
- }
-
- /* compare item. */
- if (strcmp(ast_data_retrieve_string(node, "peer/name"), "test_peer_data_provider")) {
- ao2_unlink(peers, peer);
- ao2_ref(peer, -1);
- ast_data_free(node);
- return AST_TEST_FAIL;
- }
-
- if (strcmp(ast_data_retrieve_string(node, "peer/type"), "user")) {
- ao2_unlink(peers, peer);
- ao2_ref(peer, -1);
- ast_data_free(node);
- return AST_TEST_FAIL;
- }
-
- if (ast_data_retrieve_int(node, "peer/call_limit") != 10) {
- ao2_unlink(peers, peer);
- ao2_ref(peer, -1);
- ast_data_free(node);
- return AST_TEST_FAIL;
- }
-
- /* release resources */
- ast_data_free(node);
-
- ao2_unlink(peers, peer);
- ao2_ref(peer, -1);
-
- return AST_TEST_PASS;
-}
-
/*!
* \brief Imitation TCP reception loop
*
@@ -35130,170 +35060,6 @@ AST_TEST_DEFINE(get_in_brackets_const_test)
#endif
-#define DATA_EXPORT_SIP_PEER(MEMBER) \
- MEMBER(sip_peer, name, AST_DATA_STRING) \
- MEMBER(sip_peer, secret, AST_DATA_PASSWORD) \
- MEMBER(sip_peer, md5secret, AST_DATA_PASSWORD) \
- MEMBER(sip_peer, remotesecret, AST_DATA_PASSWORD) \
- MEMBER(sip_peer, context, AST_DATA_STRING) \
- MEMBER(sip_peer, subscribecontext, AST_DATA_STRING) \
- MEMBER(sip_peer, username, AST_DATA_STRING) \
- MEMBER(sip_peer, accountcode, AST_DATA_STRING) \
- MEMBER(sip_peer, tohost, AST_DATA_STRING) \
- MEMBER(sip_peer, regexten, AST_DATA_STRING) \
- MEMBER(sip_peer, fromuser, AST_DATA_STRING) \
- MEMBER(sip_peer, fromdomain, AST_DATA_STRING) \
- MEMBER(sip_peer, fullcontact, AST_DATA_STRING) \
- MEMBER(sip_peer, cid_num, AST_DATA_STRING) \
- MEMBER(sip_peer, cid_name, AST_DATA_STRING) \
- MEMBER(sip_peer, vmexten, AST_DATA_STRING) \
- MEMBER(sip_peer, language, AST_DATA_STRING) \
- MEMBER(sip_peer, mohinterpret, AST_DATA_STRING) \
- MEMBER(sip_peer, mohsuggest, AST_DATA_STRING) \
- MEMBER(sip_peer, parkinglot, AST_DATA_STRING) \
- MEMBER(sip_peer, useragent, AST_DATA_STRING) \
- MEMBER(sip_peer, mwi_from, AST_DATA_STRING) \
- MEMBER(sip_peer, engine, AST_DATA_STRING) \
- MEMBER(sip_peer, unsolicited_mailbox, AST_DATA_STRING) \
- MEMBER(sip_peer, is_realtime, AST_DATA_BOOLEAN) \
- MEMBER(sip_peer, host_dynamic, AST_DATA_BOOLEAN) \
- MEMBER(sip_peer, autoframing, AST_DATA_BOOLEAN) \
- MEMBER(sip_peer, inuse, AST_DATA_INTEGER) \
- MEMBER(sip_peer, ringing, AST_DATA_INTEGER) \
- MEMBER(sip_peer, onhold, AST_DATA_INTEGER) \
- MEMBER(sip_peer, call_limit, AST_DATA_INTEGER) \
- MEMBER(sip_peer, t38_maxdatagram, AST_DATA_INTEGER) \
- MEMBER(sip_peer, maxcallbitrate, AST_DATA_INTEGER) \
- MEMBER(sip_peer, rtptimeout, AST_DATA_SECONDS) \
- MEMBER(sip_peer, rtpholdtimeout, AST_DATA_SECONDS) \
- MEMBER(sip_peer, rtpkeepalive, AST_DATA_SECONDS) \
- MEMBER(sip_peer, lastms, AST_DATA_MILLISECONDS) \
- MEMBER(sip_peer, maxms, AST_DATA_MILLISECONDS) \
- MEMBER(sip_peer, qualifyfreq, AST_DATA_MILLISECONDS) \
- MEMBER(sip_peer, timer_t1, AST_DATA_MILLISECONDS) \
- MEMBER(sip_peer, timer_b, AST_DATA_MILLISECONDS) \
- MEMBER(sip_peer, description, AST_DATA_STRING)
-
-AST_DATA_STRUCTURE(sip_peer, DATA_EXPORT_SIP_PEER);
-
-static int peers_data_provider_get(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- struct sip_peer *peer;
- struct ao2_iterator i;
- struct ast_data *data_peer, *data_peer_mailboxes = NULL, *data_peer_mailbox, *enum_node;
- struct ast_data *data_sip_options;
- int total_mailboxes, x;
- struct sip_mailbox *mailbox;
-
- i = ao2_iterator_init(peers, 0);
- while ((peer = ao2_iterator_next(&i))) {
- ao2_lock(peer);
-
- data_peer = ast_data_add_node(data_root, "peer");
- if (!data_peer) {
- ao2_unlock(peer);
- ao2_ref(peer, -1);
- continue;
- }
-
- ast_data_add_structure(sip_peer, data_peer, peer);
-
- /* transfer mode */
- enum_node = ast_data_add_node(data_peer, "allowtransfer");
- if (!enum_node) {
- ao2_unlock(peer);
- ao2_ref(peer, -1);
- continue;
- }
- ast_data_add_str(enum_node, "text", transfermode2str(peer->allowtransfer));
- ast_data_add_int(enum_node, "value", peer->allowtransfer);
-
- /* transports */
- ast_data_add_str(data_peer, "transports", get_transport_list(peer->transports));
-
- /* peer type */
- if ((peer->type & SIP_TYPE_USER) && (peer->type & SIP_TYPE_PEER)) {
- ast_data_add_str(data_peer, "type", "friend");
- } else if (peer->type & SIP_TYPE_PEER) {
- ast_data_add_str(data_peer, "type", "peer");
- } else if (peer->type & SIP_TYPE_USER) {
- ast_data_add_str(data_peer, "type", "user");
- }
-
- /* mailboxes */
- total_mailboxes = 0;
- AST_LIST_TRAVERSE(&peer->mailboxes, mailbox, entry) {
- if (!total_mailboxes) {
- data_peer_mailboxes = ast_data_add_node(data_peer, "mailboxes");
- if (!data_peer_mailboxes) {
- break;
- }
- total_mailboxes++;
- }
-
- data_peer_mailbox = ast_data_add_node(data_peer_mailboxes, "mailbox");
- if (!data_peer_mailbox) {
- continue;
- }
- ast_data_add_str(data_peer_mailbox, "id", mailbox->id);
- }
-
- /* amaflags */
- enum_node = ast_data_add_node(data_peer, "amaflags");
- if (!enum_node) {
- ao2_unlock(peer);
- ao2_ref(peer, -1);
- continue;
- }
- ast_data_add_int(enum_node, "value", peer->amaflags);
- ast_data_add_str(enum_node, "text", ast_channel_amaflags2string(peer->amaflags));
-
- /* sip options */
- data_sip_options = ast_data_add_node(data_peer, "sipoptions");
- if (!data_sip_options) {
- ao2_unlock(peer);
- ao2_ref(peer, -1);
- continue;
- }
- for (x = 0 ; x < ARRAY_LEN(sip_options); x++) {
- ast_data_add_bool(data_sip_options, sip_options[x].text, peer->sipoptions & sip_options[x].id);
- }
-
- /* callingpres */
- enum_node = ast_data_add_node(data_peer, "callingpres");
- if (!enum_node) {
- ao2_unlock(peer);
- ao2_ref(peer, -1);
- continue;
- }
- ast_data_add_int(enum_node, "value", peer->callingpres);
- ast_data_add_str(enum_node, "text", ast_describe_caller_presentation(peer->callingpres));
-
- /* codecs */
- ast_data_add_codecs(data_peer, "codecs", peer->caps);
-
- if (!ast_data_search_match(search, data_peer)) {
- ast_data_remove_node(data_root, data_peer);
- }
-
- ao2_unlock(peer);
- ao2_ref(peer, -1);
- }
- ao2_iterator_destroy(&i);
-
- return 0;
-}
-
-static const struct ast_data_handler peers_data_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = peers_data_provider_get
-};
-
-static const struct ast_data_entry sip_data_providers[] = {
- AST_DATA_ENTRY("asterisk/channel/sip/peers", &peers_data_provider),
-};
-
static const struct ast_sip_api_tech chan_sip_api_provider = {
.version = AST_SIP_API_VERSION,
.name = "chan_sip",
@@ -35414,15 +35180,11 @@ static int load_module(void)
}
#ifdef TEST_FRAMEWORK
- AST_TEST_REGISTER(test_sip_peers_get);
AST_TEST_REGISTER(test_sip_mwi_subscribe_parse);
AST_TEST_REGISTER(test_tcp_message_fragmentation);
AST_TEST_REGISTER(get_in_brackets_const_test);
#endif
- /* Register AstData providers */
- ast_data_register_multiple(sip_data_providers, ARRAY_LEN(sip_data_providers));
-
/* Register all CLI functions for SIP */
ast_cli_register_multiple(cli_sip, ARRAY_LEN(cli_sip));
@@ -35549,14 +35311,10 @@ static int unload_module(void)
#ifdef TEST_FRAMEWORK
ast_unregister_application(app_sipsendcustominfo);
- AST_TEST_UNREGISTER(test_sip_peers_get);
AST_TEST_UNREGISTER(test_sip_mwi_subscribe_parse);
AST_TEST_UNREGISTER(test_tcp_message_fragmentation);
AST_TEST_UNREGISTER(get_in_brackets_const_test);
#endif
- /* Unregister all the AstData providers */
- ast_data_unregister(NULL);
-
/* Unregister CLI commands */
ast_cli_unregister_multiple(cli_sip, ARRAY_LEN(cli_sip));
diff --git a/channels/pjsip/cli_commands.c b/channels/pjsip/cli_commands.c
index fc14b25a8..33d0e02c1 100644
--- a/channels/pjsip/cli_commands.c
+++ b/channels/pjsip/cli_commands.c
@@ -342,8 +342,9 @@ static int cli_channelstats_print_body(void *obj, void *arg, int flags)
const struct ast_channel_snapshot *snapshot = obj;
struct ast_channel *channel = ast_channel_get_by_name(snapshot->name);
struct ast_sip_channel_pvt *cpvt = channel ? ast_channel_tech_pvt(channel) : NULL;
- struct chan_pjsip_pvt *pvt = cpvt ? cpvt->pvt : NULL;
- struct ast_sip_session_media *media = pvt ? pvt->media[SIP_MEDIA_AUDIO] : NULL;
+ struct ast_sip_session *session;
+ struct ast_sip_session_media *media;
+ struct ast_rtp_instance *rtp;
struct ast_rtp_instance_stats stats;
char *print_name = NULL;
char *print_time = alloca(32);
@@ -351,29 +352,46 @@ static int cli_channelstats_print_body(void *obj, void *arg, int flags)
ast_assert(context->output_buffer != NULL);
+ if (!channel) {
+ ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+ return -1;
+ }
+
+ ast_channel_lock(channel);
+
+ session = cpvt->session;
+ if (!session) {
+ ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+ ast_channel_unlock(channel);
+ ao2_cleanup(channel);
+ return -1;
+ }
+
+ media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
if (!media || !media->rtp) {
ast_str_append(&context->output_buffer, 0, " %s not valid\n", snapshot->name);
+ ast_channel_unlock(channel);
ao2_cleanup(channel);
return -1;
}
+ rtp = ao2_bump(media->rtp);
+
codec_in_use[0] = '\0';
- if (channel) {
- ast_channel_lock(channel);
- if (ast_channel_rawreadformat(channel)) {
- ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use));
- }
- ast_channel_unlock(channel);
+ if (ast_channel_rawreadformat(channel)) {
+ ast_copy_string(codec_in_use, ast_format_get_name(ast_channel_rawreadformat(channel)), sizeof(codec_in_use));
}
+ ast_channel_unlock(channel);
+
print_name = ast_strdupa(snapshot->name);
/* Skip the PJSIP/. We know what channel type it is and we need the space. */
print_name += 6;
ast_format_duration_hh_mm_ss(ast_tvnow().tv_sec - snapshot->creationtime.tv_sec, print_time, 32);
- if (ast_rtp_instance_get_stats(media->rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
+ if (ast_rtp_instance_get_stats(rtp, &stats, AST_RTP_INSTANCE_STAT_ALL)) {
ast_str_append(&context->output_buffer, 0, "%s direct media\n", snapshot->name);
} else {
ast_str_append(&context->output_buffer, 0,
@@ -398,6 +416,7 @@ static int cli_channelstats_print_body(void *obj, void *arg, int flags)
);
}
+ ao2_cleanup(rtp);
ao2_cleanup(channel);
return 0;
diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c
index e2c78cd87..59ca9d791 100644
--- a/channels/pjsip/dialplan_functions.c
+++ b/channels/pjsip/dialplan_functions.c
@@ -437,6 +437,7 @@
#include "asterisk/acl.h"
#include "asterisk/app.h"
#include "asterisk/channel.h"
+#include "asterisk/stream.h"
#include "asterisk/format.h"
#include "asterisk/pbx.h"
#include "asterisk/res_pjsip.h"
@@ -461,8 +462,8 @@ static const char *t38state_to_string[T38_MAX_ENUM] = {
static int channel_read_rtp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt;
- struct ast_sip_session_media *media = NULL;
+ struct ast_sip_session *session;
+ struct ast_sip_session_media *media;
struct ast_sockaddr addr;
if (!channel) {
@@ -470,9 +471,9 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch
return -1;
}
- pvt = channel->pvt;
- if (!pvt) {
- ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan));
+ session = channel->session;
+ if (!session) {
+ ast_log(AST_LOG_WARNING, "Channel %s has no session!\n", ast_channel_name(chan));
return -1;
}
@@ -482,9 +483,9 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch
}
if (ast_strlen_zero(field) || !strcmp(field, "audio")) {
- media = pvt->media[SIP_MEDIA_AUDIO];
+ media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
} else if (!strcmp(field, "video")) {
- media = pvt->media[SIP_MEDIA_VIDEO];
+ media = session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];
} else {
ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtp' information\n", field);
return -1;
@@ -522,17 +523,17 @@ static int channel_read_rtp(struct ast_channel *chan, const char *type, const ch
static int channel_read_rtcp(struct ast_channel *chan, const char *type, const char *field, char *buf, size_t buflen)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- struct chan_pjsip_pvt *pvt;
- struct ast_sip_session_media *media = NULL;
+ struct ast_sip_session *session;
+ struct ast_sip_session_media *media;
if (!channel) {
ast_log(AST_LOG_WARNING, "Channel %s has no pvt!\n", ast_channel_name(chan));
return -1;
}
- pvt = channel->pvt;
- if (!pvt) {
- ast_log(AST_LOG_WARNING, "Channel %s has no chan_pjsip pvt!\n", ast_channel_name(chan));
+ session = channel->session;
+ if (!session) {
+ ast_log(AST_LOG_WARNING, "Channel %s has no session!\n", ast_channel_name(chan));
return -1;
}
@@ -542,9 +543,9 @@ static int channel_read_rtcp(struct ast_channel *chan, const char *type, const c
}
if (ast_strlen_zero(field) || !strcmp(field, "audio")) {
- media = pvt->media[SIP_MEDIA_AUDIO];
+ media = session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
} else if (!strcmp(field, "video")) {
- media = pvt->media[SIP_MEDIA_VIDEO];
+ media = session->active_media_state->default_session[AST_MEDIA_TYPE_VIDEO];
} else {
ast_log(AST_LOG_WARNING, "Unknown media type field '%s' for 'rtcp' information\n", field);
return -1;
@@ -924,22 +925,117 @@ int pjsip_acf_dial_contacts_read(struct ast_channel *chan, const char *cmd, char
return 0;
}
+/*! \brief Session refresh state information */
+struct session_refresh_state {
+ /*! \brief Created proposed media state */
+ struct ast_sip_session_media_state *media_state;
+};
+
+/*! \brief Destructor for session refresh information */
+static void session_refresh_state_destroy(void *obj)
+{
+ struct session_refresh_state *state = obj;
+
+ ast_sip_session_media_state_free(state->media_state);
+ ast_free(obj);
+}
+
+/*! \brief Datastore for attaching session refresh state information */
+static const struct ast_datastore_info session_refresh_datastore = {
+ .type = "pjsip_session_refresh",
+ .destroy = session_refresh_state_destroy,
+};
+
+/*! \brief Helper function which retrieves or allocates a session refresh state information datastore */
+static struct session_refresh_state *session_refresh_state_get_or_alloc(struct ast_sip_session *session)
+{
+ RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "pjsip_session_refresh"), ao2_cleanup);
+ struct session_refresh_state *state;
+
+ /* While the datastore refcount is decremented this is operating in the serializer so it will remain valid regardless */
+ if (datastore) {
+ return datastore->data;
+ }
+
+ if (!(datastore = ast_sip_session_alloc_datastore(&session_refresh_datastore, "pjsip_session_refresh"))
+ || !(datastore->data = ast_calloc(1, sizeof(struct session_refresh_state)))
+ || ast_sip_session_add_datastore(session, datastore)) {
+ return NULL;
+ }
+
+ state = datastore->data;
+ state->media_state = ast_sip_session_media_state_alloc();
+ if (!state->media_state) {
+ ast_sip_session_remove_datastore(session, "pjsip_session_refresh");
+ return NULL;
+ }
+ state->media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);
+ if (!state->media_state->topology) {
+ ast_sip_session_remove_datastore(session, "pjsip_session_refresh");
+ return NULL;
+ }
+
+ datastore->data = state;
+
+ return state;
+}
+
static int media_offer_read_av(struct ast_sip_session *session, char *buf,
size_t len, enum ast_media_type media_type)
{
+ struct ast_stream_topology *topology;
int idx;
+ struct ast_stream *stream = NULL;
+ struct ast_format_cap *caps;
size_t accum = 0;
+ if (session->inv_session->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) {
+ struct session_refresh_state *state;
+
+ /* As we've already answered we need to store our media state until we are ready to send it */
+ state = session_refresh_state_get_or_alloc(session);
+ if (!state) {
+ return -1;
+ }
+ topology = state->media_state->topology;
+ } else {
+ /* The session is not yet up so we are initially answering or offering */
+ if (!session->pending_media_state->topology) {
+ session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);
+ if (!session->pending_media_state->topology) {
+ return -1;
+ }
+ }
+ topology = session->pending_media_state->topology;
+ }
+
+ /* Find the first suitable stream */
+ for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) {
+ stream = ast_stream_topology_get_stream(topology, idx);
+
+ if (ast_stream_get_type(stream) != media_type ||
+ ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
+ stream = NULL;
+ continue;
+ }
+
+ break;
+ }
+
+ /* If no suitable stream then exit early */
+ if (!stream) {
+ buf[0] = '\0';
+ return 0;
+ }
+
+ caps = ast_stream_get_formats(stream);
+
/* Note: buf is not terminated while the string is being built. */
- for (idx = 0; idx < ast_format_cap_count(session->req_caps); ++idx) {
+ for (idx = 0; idx < ast_format_cap_count(caps); ++idx) {
struct ast_format *fmt;
size_t size;
- fmt = ast_format_cap_get_format(session->req_caps, idx);
- if (ast_format_get_type(fmt) != media_type) {
- ao2_ref(fmt, -1);
- continue;
- }
+ fmt = ast_format_cap_get_format(caps, idx);
/* Add one for a comma or terminator */
size = strlen(ast_format_get_name(fmt)) + 1;
@@ -973,9 +1069,43 @@ struct media_offer_data {
static int media_offer_write_av(void *obj)
{
struct media_offer_data *data = obj;
+ struct ast_stream_topology *topology;
+ struct ast_stream *stream;
+ struct ast_format_cap *caps;
- ast_format_cap_remove_by_type(data->session->req_caps, data->media_type);
- ast_format_cap_update_by_allow_disallow(data->session->req_caps, data->value, 1);
+ if (data->session->inv_session->dlg->state == PJSIP_DIALOG_STATE_ESTABLISHED) {
+ struct session_refresh_state *state;
+
+ /* As we've already answered we need to store our media state until we are ready to send it */
+ state = session_refresh_state_get_or_alloc(data->session);
+ if (!state) {
+ return -1;
+ }
+ topology = state->media_state->topology;
+ } else {
+ /* The session is not yet up so we are initially answering or offering */
+ if (!data->session->pending_media_state->topology) {
+ data->session->pending_media_state->topology = ast_stream_topology_clone(data->session->endpoint->media.topology);
+ if (!data->session->pending_media_state->topology) {
+ return -1;
+ }
+ }
+ topology = data->session->pending_media_state->topology;
+ }
+
+ /* XXX This method won't work when it comes time to do multistream support. The proper way to do this
+ * will either be to
+ * a) Alter all media streams of a particular type.
+ * b) Change the dialplan function to be able to specify which stream to alter and alter only that
+ * one stream
+ */
+ stream = ast_stream_topology_get_first_stream_by_type(topology, data->media_type);
+ if (!stream) {
+ return 0;
+ }
+ caps = ast_stream_get_formats(stream);
+ ast_format_cap_remove_by_type(caps, data->media_type);
+ ast_format_cap_update_by_allow_disallow(caps, data->value, 1);
return 0;
}
@@ -1068,9 +1198,18 @@ static int sip_session_response_cb(struct ast_sip_session *session, pjsip_rx_dat
static int refresh_write_cb(void *obj)
{
struct refresh_data *data = obj;
+ struct session_refresh_state *state;
+
+ state = session_refresh_state_get_or_alloc(data->session);
+ if (!state) {
+ return -1;
+ }
ast_sip_session_refresh(data->session, NULL, NULL,
- sip_session_response_cb, data->method, 1);
+ sip_session_response_cb, data->method, 1, state->media_state);
+
+ state->media_state = NULL;
+ ast_sip_session_remove_datastore(data->session, "pjsip_session_refresh");
return 0;
}
diff --git a/channels/pjsip/include/chan_pjsip.h b/channels/pjsip/include/chan_pjsip.h
index b229a0487..1fee86419 100644
--- a/channels/pjsip/include/chan_pjsip.h
+++ b/channels/pjsip/include/chan_pjsip.h
@@ -34,25 +34,12 @@ struct transport_info_data {
pj_sockaddr local_addr;
};
-/*!
- * \brief Positions of various media
- */
-enum sip_session_media_position {
- /*! \brief First is audio */
- SIP_MEDIA_AUDIO = 0,
- /*! \brief Second is video */
- SIP_MEDIA_VIDEO,
- /*! \brief Last is the size for media details */
- SIP_MEDIA_SIZE,
-};
/*!
* \brief The PJSIP channel driver pvt, stored in the \ref ast_sip_channel_pvt
* data structure
*/
struct chan_pjsip_pvt {
- /*! \brief The available media sessions */
- struct ast_sip_session_media *media[SIP_MEDIA_SIZE];
};
#endif /* _CHAN_PJSIP_HEADER */
diff --git a/configs/basic-pbx/modules.conf b/configs/basic-pbx/modules.conf
index 8de58bf2d..9711745ed 100644
--- a/configs/basic-pbx/modules.conf
+++ b/configs/basic-pbx/modules.conf
@@ -77,7 +77,6 @@ load = res_pjsip_exten_state.so
load = res_pjsip_header_funcs.so
load = res_pjsip_logger.so
load = res_pjsip_messaging.so
-load = res_pjsip_multihomed.so
load = res_pjsip_mwi_body_generator.so
load = res_pjsip_mwi.so
load = res_pjsip_nat.so
diff --git a/configs/samples/cdr.conf.sample b/configs/samples/cdr.conf.sample
index e175a2a76..1d0af7864 100644
--- a/configs/samples/cdr.conf.sample
+++ b/configs/samples/cdr.conf.sample
@@ -17,7 +17,7 @@
; party. Setting this to "yes" will make calls to extensions that don't answer
; and don't set a B side channel (such as by using the Dial application)
; receive CDR log entries. If this option is set to "no", then those log
-; entries will not be created. Unasnwered Calls which get offered to an
+; entries will not be created. Unanswered Calls which get offered to an
; outgoing line will always receive log entries regardless of this option, and
; that is the intended behaviour.
;unanswered = no
diff --git a/configs/samples/confbridge.conf.sample b/configs/samples/confbridge.conf.sample
index 0e07f6b16..265b95342 100644
--- a/configs/samples/confbridge.conf.sample
+++ b/configs/samples/confbridge.conf.sample
@@ -218,6 +218,12 @@ type=bridge
; Default is en (English).
;regcontext=conferences ; The name of the context into which to register conference names as extensions.
+;video_update_discard=2000 ; Amount of time (in milliseconds) to discard video update requests after sending a video
+ ; update request. Default is 2000. A video update request is a request for a full video
+ ; intra-frame. Clients can request this if they require a full frame in order to decode
+ ; the video stream. Since a full frame can be large limiting how often they occur can
+ ; reduce bandwidth usage at the cost of increasing how long it may take a newly joined
+ ; channel to receive the video stream.
; All sounds in the conference are customizable using the bridge profile options below.
; Simply state the option followed by the filename or full path of the filename after
diff --git a/configs/samples/config_test.conf.sample b/configs/samples/config_test.conf.sample
index 2fff45ece..b7cb21292 100644
--- a/configs/samples/config_test.conf.sample
+++ b/configs/samples/config_test.conf.sample
@@ -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
diff --git a/configs/samples/musiconhold.conf.sample b/configs/samples/musiconhold.conf.sample
index b2980fc1d..741bde603 100644
--- a/configs/samples/musiconhold.conf.sample
+++ b/configs/samples/musiconhold.conf.sample
@@ -97,3 +97,26 @@ directory=moh
;mode=custom
;directory=/var/lib/asterisk/mohmp3
;application=/site/sw/bin/madplay -Q -o raw:- --mono -R 8000 -a -12
+
+; By default, when res_musiconhold reloads or unloads, it sends a HUP signal
+; to custom applications (and all descendants), waits 100ms, then sends a
+; TERM signal, waits 100ms, then finally sends a KILL signal. An application
+; which is interacting with an external device and/or spawns children of its
+; own may not be able to exit cleanly in the default times, expecially if sent
+; a KILL signal, or if it's children are getting signals directly from
+; res_musiconhoild. To allow extra time, the 'kill_escalation_delay'
+; class option can be used to set the number of milliseconds res_musiconhold
+; waits before escalating kill signals, with the default being the current
+; 100ms. To control to whom the signals are sent, the "kill_method"
+; class option can be set to "process_group" (the default, existing behavior),
+; which sends signals to the application and its descendants directly, or
+; "process" which sends signals only to the application itself.
+
+;[sox_from_device]
+;mode=custom
+;directory=/var/lib/asterisk/mohmp3
+;application=/usr/bin/sox -q -t alsa -c 2 -r 48000 hw:1 -c 1 -r 8000 -t raw -s -
+; Wait 500ms before escalating kill signals
+;kill_escalation_delay=500
+; Send signals to just the child process instead of all descendants
+;kill_method=process
diff --git a/configs/samples/pjsip.conf.sample b/configs/samples/pjsip.conf.sample
index a992ff8b6..3c3e52a05 100644
--- a/configs/samples/pjsip.conf.sample
+++ b/configs/samples/pjsip.conf.sample
@@ -608,8 +608,15 @@
;direct_media_glare_mitigation=none ; Mitigation of direct media re INVITE
; glare (default: "none")
;direct_media_method=invite ; Direct Media method type (default: "invite")
-;connected_line_method=invite ; Connected line method type (default:
- ; "invite")
+;connected_line_method=invite ; Connected line method type.
+ ; When set to "invite", check the remote's
+ ; Allow header and if UPDATE is allowed, send
+ ; UPDATE instead of INVITE to avoid SDP
+ ; renegotiation. If UPDATE is not Allowed,
+ ; send INVITE.
+ ; If set to "update", send UPDATE regardless
+ ; of what the remote Allows.
+ ; (default: "invite")
;direct_media=yes ; Determines whether media may flow directly between
; endpoints (default: "yes")
;disable_direct_media_on_nat=no ; Disable direct media session refreshes when
@@ -774,6 +781,23 @@
; transfer (default: "yes"). The value "no" is useful
; for some SIP phones (Mitel/Aastra, Snom) which expect
; a sip/frag "200 OK" after REFER has been accepted.
+;notify_early_inuse_ringing = ; Whether to notifies dialog-info 'early'
+ ; on INUSE && RINGING state (default: "no").
+ ; The value "yes" is useful for some SIP phones
+ ; (Cisco SPA) to be able to indicate and pick up
+ ; ringing devices.
+;max_audio_streams= ; The maximum number of allowed negotiated audio streams
+ ; (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]
diff --git a/configs/samples/voicemail.conf.sample b/configs/samples/voicemail.conf.sample
index f8221eebe..84e83a344 100644
--- a/configs/samples/voicemail.conf.sample
+++ b/configs/samples/voicemail.conf.sample
@@ -227,6 +227,9 @@ pagerdateformat=%A, %B %d, %Y at %r
;imapclosetimeout=60 ; The TCP close timeout (in seconds)
;imapreadtimeout=60 ; The TCP read timeout (in seconds)
;imapwritetimeout=60 ; The TCP write timeout (in seconds)
+;imap_poll_logout=no ; If pollmailboxes=yes, then specify whether need to
+ ; disconnect from the IMAP server after polling.
+ ; Default: no
; -----------------------------------------------------------------------------
;
diff --git a/configure b/configure
index 361af8884..5bb75236b 100755
--- a/configure
+++ b/configure
@@ -1219,6 +1219,7 @@ PJPROJECT_LIB
PBX_PJPROJECT
PJPROJECT_DIR
PJPROJECT_BUNDLED
+PJPROJECT_CONFIGURE_OPTS
AST_C_COMPILER_FAMILY
AST_CLANG_BLOCKS
AST_CLANG_BLOCKS_LIBS
@@ -1484,6 +1485,7 @@ CXX
CXXFLAGS
CCC
CXXCPP
+PJPROJECT_CONFIGURE_OPTS
PKG_CONFIG
PKG_CONFIG_PATH
PKG_CONFIG_LIBDIR
@@ -2242,6 +2244,8 @@ Some influential environment variables:
CXX C++ compiler command
CXXFLAGS C++ compiler flags
CXXCPP C++ preprocessor
+ PJPROJECT_CONFIGURE_OPTS
+ Additional configure options to pass to bundled pjproject
PKG_CONFIG path to pkg-config utility
PKG_CONFIG_PATH
directories to add to pkg-config's search path
@@ -9315,20 +9319,33 @@ $as_echo "configuring" >&6; }
as_fn_error $? "cat is required to build bundled pjproject" "$LINENO" 5
fi
+
+ this_host=$(./config.sub $(./config.guess))
+ if test "$build" != "$this_host" ; then
+ PJPROJECT_CONFIGURE_OPTS+=" --build=$build"
+ fi
+ if test "$host" != "$this_host" ; then
+ PJPROJECT_CONFIGURE_OPTS+=" --host=$host"
+ fi
+
export TAR PATCH SED NM EXTERNALS_CACHE_DIR DOWNLOAD_TO_STDOUT DOWNLOAD_TIMEOUT DOWNLOAD MD5 CAT
- ${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} EXTERNALS_CACHE_DIR=${EXTERNALS_CACHE_DIR} configure
+ export NOISY_BUILD
+ ${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} \
+ PJPROJECT_CONFIGURE_OPTS="$PJPROJECT_CONFIGURE_OPTS" \
+ EXTERNALS_CACHE_DIR="${EXTERNALS_CACHE_DIR}" \
+ configure
if test $? -ne 0 ; then
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: failed" >&5
$as_echo "failed" >&6; }
{ $as_echo "$as_me:${as_lineno-$LINENO}: Unable to configure ${PJPROJECT_DIR}" >&5
$as_echo "$as_me: Unable to configure ${PJPROJECT_DIR}" >&6;}
- as_fn_error $? "Run \"${GNU_MAKE} -C ${PJPROJECT_DIR} NOISY_BUILD=yes configure\" to see error details." "$LINENO" 5
+ as_fn_error $? "Re-run the ./configure command with 'NOISY_BUILD=yes' appended to see error details." "$LINENO" 5
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for bundled pjproject" >&5
$as_echo_n "checking for bundled pjproject... " >&6; }
- PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} EXTERNALS_CACHE_DIR=${EXTERNALS_CACHE_DIR} echo_cflags)
+ PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} PJPROJECT_CONFIGURE_OPTS="$PJPROJECT_CONFIGURE_OPTS" EXTERNALS_CACHE_DIR="${EXTERNALS_CACHE_DIR}" echo_cflags)
PJPROJECT_CFLAGS="$PJPROJECT_INCLUDE"
PBX_PJPROJECT=1
@@ -31905,7 +31922,7 @@ if eval \${$as_ac_Lib+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
-LIBS="-lcpg ${pbxlibdir} -lcfg $LIBS"
+LIBS="-lcpg ${pbxlibdir} -lcpg $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
@@ -31947,7 +31964,7 @@ fi
# now check for the header.
if test "${AST_COROSYNC_FOUND}" = "yes"; then
- COROSYNC_LIB="${pbxlibdir} -lcpg -lcfg"
+ COROSYNC_LIB="${pbxlibdir} -lcpg -lcpg"
# if --with-COROSYNC=DIR has been specified, use it.
if test "x${COROSYNC_DIR}" != "x"; then
COROSYNC_INCLUDE="-I${COROSYNC_DIR}/include"
diff --git a/configure.ac b/configure.ac
index ecbe87093..a2eaab4ad 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2414,7 +2414,7 @@ fi
AST_EXT_LIB_CHECK([CODEC2], [codec2], [codec2_create], [codec2/codec2.h])
-AST_EXT_LIB_CHECK([COROSYNC], [cpg], [cpg_join], [corosync/cpg.h], [-lcfg])
+AST_EXT_LIB_CHECK([COROSYNC], [cpg], [cpg_join], [corosync/cpg.h], [-lcpg])
AST_EXT_LIB_CHECK([COROSYNC_CFG_STATE_TRACK], [cfg], [corosync_cfg_state_track], [corosync/cfg.h], [-lcfg])
AST_EXT_LIB_CHECK([SPEEX], [speex], [speex_encode], [speex/speex.h], [-lm])
diff --git a/contrib/ast-db-manage/config/versions/164abbd708c_add_auto_info_to_endpoint_dtmf_mode.py b/contrib/ast-db-manage/config/versions/164abbd708c_add_auto_info_to_endpoint_dtmf_mode.py
new file mode 100644
index 000000000..20cab2f38
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/164abbd708c_add_auto_info_to_endpoint_dtmf_mode.py
@@ -0,0 +1,58 @@
+"""Add auto_info to endpoint dtmf_mode
+
+Revision ID: 164abbd708c
+Revises: 39959b9c2566
+Create Date: 2017-06-19 13:55:15.354706
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '164abbd708c'
+down_revision = '39959b9c2566'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+OLD_ENUM = ['rfc4733', 'inband', 'info', 'auto']
+NEW_ENUM = ['rfc4733', 'inband', 'info', 'auto', 'auto_info']
+
+old_type = sa.Enum(*OLD_ENUM, name='pjsip_dtmf_mode_values_v2')
+new_type = sa.Enum(*NEW_ENUM, name='pjsip_dtmf_mode_values_v3')
+
+def upgrade():
+ context = op.get_context()
+
+ # Upgrading to this revision WILL clear your directmedia values.
+ if context.bind.dialect.name != 'postgresql':
+ op.alter_column('ps_endpoints', 'dtmf_mode',
+ type_=new_type,
+ existing_type=old_type)
+ else:
+ enum = ENUM('rfc4733', 'inband', 'info', 'auto', 'auto_info',
+ name='pjsip_dtmf_mode_values_v3')
+ enum.create(op.get_bind(), checkfirst=False)
+
+ op.execute('ALTER TABLE ps_endpoints ALTER COLUMN dtmf_mode TYPE'
+ ' pjsip_dtmf_mode_values_v3 USING'
+ ' dtmf_mode::text::pjsip_dtmf_mode_values_v3')
+
+ ENUM(name="pjsip_dtmf_mode_values_v2").drop(op.get_bind(), checkfirst=False)
+
+def downgrade():
+ context = op.get_context()
+
+ if context.bind.dialect.name != 'postgresql':
+ op.alter_column('ps_endpoints', 'dtmf_mode',
+ type_=old_type,
+ existing_type=new_type)
+ else:
+ enum = ENUM('rfc4733', 'inband', 'info', 'auto',
+ name='pjsip_dtmf_mode_values_v2')
+ enum.create(op.get_bind(), checkfirst=False)
+
+ op.execute('ALTER TABLE ps_endpoints ALTER COLUMN dtmf_mode TYPE'
+ ' pjsip_dtmf_mode_values USING'
+ ' dtmf_mode::text::pjsip_dtmf_mode_values_v2')
+
+ ENUM(name="pjsip_dtmf_mode_values_v3").drop(op.get_bind(), checkfirst=False)
diff --git a/contrib/ast-db-manage/config/versions/39959b9c2566_pjsip_stream_maximum.py b/contrib/ast-db-manage/config/versions/39959b9c2566_pjsip_stream_maximum.py
new file mode 100644
index 000000000..a091272b0
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/39959b9c2566_pjsip_stream_maximum.py
@@ -0,0 +1,24 @@
+"""pjsip_stream_maximum
+
+Revision ID: 39959b9c2566
+Revises: d7983954dd96
+Create Date: 2017-06-15 13:18:12.372333
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '39959b9c2566'
+down_revision = 'd7983954dd96'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+ op.add_column('ps_endpoints', sa.Column('max_audio_streams', sa.Integer))
+ op.add_column('ps_endpoints', sa.Column('max_video_streams', sa.Integer))
+
+
+def downgrade():
+ op.drop_column('ps_endpoints', 'max_audio_streams')
+ op.drop_column('ps_endpoints', 'max_video_streams')
diff --git a/contrib/ast-db-manage/config/versions/d7983954dd96_add_ps_endpoints_notify_early_inuse_.py b/contrib/ast-db-manage/config/versions/d7983954dd96_add_ps_endpoints_notify_early_inuse_.py
new file mode 100644
index 000000000..e1dcdd133
--- /dev/null
+++ b/contrib/ast-db-manage/config/versions/d7983954dd96_add_ps_endpoints_notify_early_inuse_.py
@@ -0,0 +1,30 @@
+"""add ps_endpoints.notify_early_inuse_ringing
+
+Revision ID: d7983954dd96
+Revises: 86bb1efa278d
+Create Date: 2017-06-05 15:44:41.152280
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'd7983954dd96'
+down_revision = '86bb1efa278d'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+YESNO_NAME = 'yesno_values'
+YESNO_VALUES = ['yes', 'no']
+
+def upgrade():
+ ############################# Enums ##############################
+
+ # yesno_values have already been created, so use postgres enum object
+ # type to get around "already created" issue - works okay with mysql
+ yesno_values = ENUM(*YESNO_VALUES, name=YESNO_NAME, create_type=False)
+
+ op.add_column('ps_endpoints', sa.Column('notify_early_inuse_ringing', yesno_values))
+
+def downgrade():
+ op.drop_column('ps_endpoints', 'notify_early_inuse_ringing')
diff --git a/contrib/scripts/install_prereq b/contrib/scripts/install_prereq
index fb240890b..d69f5527a 100755
--- a/contrib/scripts/install_prereq
+++ b/contrib/scripts/install_prereq
@@ -26,7 +26,7 @@ PACKAGES_DEBIAN="$PACKAGES_DEBIAN libncurses-dev libz-dev libssl-dev libxml2-dev
PACKAGES_DEBIAN="$PACKAGES_DEBIAN libcurl-dev libspeex-dev libspeexdsp-dev libogg-dev libvorbis-dev libasound2-dev portaudio19-dev libcurl4-openssl-dev"
PACKAGES_DEBIAN="$PACKAGES_DEBIAN libpq-dev unixodbc-dev libsqlite0-dev libmysqlclient15-dev libneon27-dev libgmime-dev libusb-dev liblua5.1-0-dev lua5.1"
PACKAGES_DEBIAN="$PACKAGES_DEBIAN libopenh323-dev libvpb-dev libgtk2.0-dev libmysqlclient-dev libbluetooth-dev libradiusclient-ng-dev freetds-dev"
-PACKAGES_DEBIAN="$PACKAGES_DEBIAN libsnmp-dev libiksemel-dev libcorosync-dev libnewt-dev libpopt-dev libical-dev libspandsp-dev libjack-dev"
+PACKAGES_DEBIAN="$PACKAGES_DEBIAN libsnmp-dev libiksemel-dev libcorosync-dev libcpg-dev libcfg-dev libnewt-dev libpopt-dev libical-dev libspandsp-dev libjack-dev"
PACKAGES_DEBIAN="$PACKAGES_DEBIAN libresample-dev libc-client-dev binutils-dev libsrtp-dev libgsm1-dev libedit-dev doxygen libjansson-dev libldap-dev"
PACKAGES_DEBIAN="$PACKAGES_DEBIAN subversion git libxslt1-dev automake libsrtp-dev libncurses5-dev python-dev"
PACKAGES_RH="automake bzip2 gcc gcc-c++ patch ncurses-devel openssl-devel libxml2-devel unixODBC-devel libcurl-devel libogg-devel libvorbis-devel speex-devel"
diff --git a/include/asterisk/_private.h b/include/asterisk/_private.h
index b3c2b2002..e989b16fd 100644
--- a/include/asterisk/_private.h
+++ b/include/asterisk/_private.h
@@ -44,7 +44,6 @@ int ast_named_locks_init(void); /*!< Provided by named_locks.c */
int ast_file_init(void); /*!< Provided by file.c */
int ast_features_init(void); /*!< Provided by features.c */
void ast_autoservice_init(void); /*!< Provided by autoservice.c */
-int ast_data_init(void); /*!< Provided by data.c */
int ast_http_init(void); /*!< Provided by http.c */
int ast_http_reload(void); /*!< Provided by http.c */
int ast_tps_init(void); /*!< Provided by taskprocessor.c */
diff --git a/include/asterisk/bridge.h b/include/asterisk/bridge.h
index bc0e9c81e..8d5c50211 100644
--- a/include/asterisk/bridge.h
+++ b/include/asterisk/bridge.h
@@ -134,6 +134,7 @@ struct ast_bridge_video_mode {
struct ast_bridge_video_single_src_data single_src_data;
struct ast_bridge_video_talker_src_data talker_src_data;
} mode_data;
+ unsigned int video_update_discard;
};
/*!
@@ -903,6 +904,14 @@ void ast_bridge_set_talker_src_video_mode(struct ast_bridge *bridge);
void ast_bridge_set_sfu_video_mode(struct ast_bridge *bridge);
/*!
+ * \brief Set the amount of time to discard subsequent video updates after a video update has been sent
+ *
+ * \param bridge Bridge to set the minimum video update wait time on
+ * \param video_update_discard Amount of time after sending a video update that others should be discarded
+ */
+void ast_bridge_set_video_update_discard(struct ast_bridge *bridge, unsigned int video_update_discard);
+
+/*!
* \brief Update information about talker energy for talker src video mode.
*/
void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyfame);
diff --git a/include/asterisk/channel.h b/include/asterisk/channel.h
index 005803d5c..f0fe5b212 100644
--- a/include/asterisk/channel.h
+++ b/include/asterisk/channel.h
@@ -174,7 +174,7 @@ extern "C" {
#include "asterisk/linkedlists.h"
#include "asterisk/stringfields.h"
#include "asterisk/datastore.h"
-#include "asterisk/data.h"
+#include "asterisk/format_cap.h"
#include "asterisk/channelstate.h"
#include "asterisk/ccss.h"
#include "asterisk/framehook.h"
@@ -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
@@ -2219,11 +2239,12 @@ int ast_waitfordigit(struct ast_channel *c, int ms);
* Same as ast_waitfordigit() with audio fd for outputting read audio and ctrlfd to monitor for reading.
* \param c channel to wait for a digit on
* \param ms how many milliseconds to wait (<0 for indefinite).
+ * \param breakon string of DTMF digits to break upon or NULL for any.
* \param audiofd audio file descriptor to write to if audio frames are received
* \param ctrlfd control file descriptor to monitor for reading
* \return Returns 1 if ctrlfd becomes available
*/
-int ast_waitfordigit_full(struct ast_channel *c, int ms, int audiofd, int ctrlfd);
+int ast_waitfordigit_full(struct ast_channel *c, int ms, const char *breakon, int audiofd, int ctrlfd);
/*!
* \brief Reads multiple digits
@@ -3839,27 +3860,6 @@ int ast_channel_connected_line_macro(struct ast_channel *autoservice_chan, struc
int ast_channel_connected_line_sub(struct ast_channel *autoservice_chan, struct ast_channel *sub_chan, const void *connected_info, int frame);
/*!
- * \brief Insert into an astdata tree, the channel structure.
- * \param[in] tree The ast data tree.
- * \param[in] chan The channel structure to add to tree.
- * \param[in] add_bridged Add the bridged channel to the structure.
- * \retval <0 on error.
- * \retval 0 on success.
- */
-int ast_channel_data_add_structure(struct ast_data *tree, struct ast_channel *chan, int add_bridged);
-
-/*!
- * \brief Compare to channel structures using the data api.
- * \param[in] tree The search tree generated by the data api.
- * \param[in] chan The channel to compare.
- * \param[in] structure_name The name of the node of the channel structure.
- * \retval 0 The structure matches.
- * \retval 1 The structure doesn't matches.
- */
-int ast_channel_data_cmp_structure(const struct ast_data_search *tree, struct ast_channel *chan,
- const char *structure_name);
-
-/*!
* \since 1.8
* \brief Run a redirecting interception macro and update a channel's redirecting information
* \deprecated You should use the ast_channel_redirecting_sub() function instead.
diff --git a/include/asterisk/config.h b/include/asterisk/config.h
index f57966b0b..1addfa317 100644
--- a/include/asterisk/config.h
+++ b/include/asterisk/config.h
@@ -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);
diff --git a/include/asterisk/config_options.h b/include/asterisk/config_options.h
index f2a457eb5..f4c3db188 100644
--- a/include/asterisk/config_options.h
+++ b/include/asterisk/config_options.h
@@ -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
diff --git a/include/asterisk/core_local.h b/include/asterisk/core_local.h
index 8557072c6..27e924477 100644
--- a/include/asterisk/core_local.h
+++ b/include/asterisk/core_local.h
@@ -42,36 +42,37 @@ struct stasis_message_type;
/* ------------------------------------------------------------------- */
/*!
- * \brief Lock the "chan" and "owner" channels (and return them) on the base
- * private structure as well as the base private structure itself.
+ * \brief Add a reference to the local channel's private tech, lock the local channel's
+ * private base, and add references and lock both sides of the local channel.
*
- * \note This also adds references to each of the above mentioned elements and
- * also the underlying private local structure.
* \note None of these locks should be held prior to calling this function.
- * \note To undo this process call ast_local_unlock_all.
+ * \note To undo this process call ast_local_unlock_all2.
*
- * \since 13.8.0
+ * \since 13.17.0, 14.6.0
*
* \param chan Must be a local channel
- * \param outchan The local channel's "chan" channel
- * \param outowner The local channel's "owner" channel
+ * \param tech_pvt [out] channel's private tech (ref and lock added)
+ * \param base_chan [out] One side of the local channel (ref and lock added)
+ * \param base_owner [out] Other side of the local channel (ref and lock added)
*/
-void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan,
- struct ast_channel **outowner);
+void ast_local_lock_all(struct ast_channel *chan, void **tech_pvt,
+ struct ast_channel **base_chan, struct ast_channel **base_owner);
/*!
- * \brief Unlock the "chan" and "owner" channels on the base private structure
- * as well as the base private structure itself.
+ * \brief Remove a reference to the given local channel's private tech, unlock the given
+ * local channel's private base, and remove references and unlock both sides of
+ * given the local channel.
*
- * \note This also removes references to each of the above mentioned elements and
- * also the underlying private local structure.
- * \note This function should be used in conjunction with ast_local_lock_all.
+ * \note This function should be used in conjunction with ast_local_lock_all2.
*
- * \since 13.8.0
+ * \since 13.17.0, 14.6.0
*
- * \param chan Must be a local channel
+ * \param tech_pvt channel's private tech (ref and lock removed)
+ * \param base_chan One side of the local channel (ref and lock removed)
+ * \param base_owner Other side of the local channel (ref and lock removed)
*/
-void ast_local_unlock_all(struct ast_channel *chan);
+void ast_local_unlock_all(void *tech_pvt, struct ast_channel *base_chan,
+ struct ast_channel *base_owner);
/*!
* \brief Get the other local channel in the pair.
diff --git a/include/asterisk/data.h b/include/asterisk/data.h
deleted file mode 100644
index d6da1f7d6..000000000
--- a/include/asterisk/data.h
+++ /dev/null
@@ -1,828 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2009, Eliel C. Sardanons (LU1ALY) <eliels@gmail.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*!
- * \file
- * \brief Data retrieval API.
- * \author Brett Bryant <brettbryant@gmail.com>
- * \author Eliel C. Sardanons (LU1ALY) <eliels@gmail.com>
- * \arg \ref AstDataRetrieval
- */
-
-#ifndef ASTERISK_DATA_H
-#define ASTERISK_DATA_H
-
-#include "asterisk/frame.h"
-#include "asterisk/format_cap.h"
-
-/*!
- * \page AstDataRetrieval The Asterisk DATA retrieval API.
- *
- * This module implements an abstraction for retrieving asterisk data and
- * export it.
- *
- * \section USAGE
- *
- * \subsection Provider
- *
- * \b Register
- *
- * To register a callback use:
- *
- * \code
- * static const struct ast_data_handler callback_handler = {
- * .get = callback_handler_get_function,
- * };
- *
- * ast_data_register("/node/path", &callback_handler);
- * \endcode
- *
- * If you instead want to register multiple nodes at once use:
- * \code
- * static const struct ast_data_handler handler_struct1 = {
- * .get = handler_callback_read,
- * };
- * ... other handlers ...
- *
- * static const struct ast_data_entry list_providers[] = {
- * AST_DATA_ENTRY("/path1/node1", &handler_struct1),
- * AST_DATA_ENTRY("/path2/node2", &handler_struct2),
- * AST_DATA_ENTRY("/path3/node3", &handler_struct3),
- * };
- *
- * ...
- *
- * ast_data_register_multiple(list_providers, ARRAY_LEN(list_providers));
- * \endcode
- *
- * \b Unregister
- *
- * To unregister a callback function already registered you can just call:
- *
- * \code
- * ast_data_unregister(NULL);
- * \endcode
- * And every node registered by the current module (file) will be unregistered.
- * If you want to unregister a specific node use:
- *
- * \code
- * ast_data_unregister("/node/path");
- * \endcode
- *
- * \b Implementation
- *
- * A simple callback function implementation:
- *
- * \code
- * #include <data.h>
- *
- * struct test_structure {
- * int a;
- * double b;
- * };
- *
- * DATA_EXPORT_TEST_STRUCTURE(MEMBER) \
- * MEMBER(test_structure, a, AST_DATA_INTEGER) \
- * MEMBER(test_structure, b, AST_DATA_DOUBLE)
- *
- * AST_DATA_STRUCTURE(test_structure, DATA_EXPORT_TEST_STRUCTURE)
- *
- * static int my_callback_function(struct ast_data_search *search,
- * struct ast_data *root_node)
- * {
- * struct ast_data *internal_node;
- * struct test_structure ts = {
- * .a = 10,
- * .b = 20
- * };
- *
- * internal_node = ast_data_add_node(root_node, "test_node");
- * if (!internal_node) {
- * return -1;
- * }
- *
- * ast_data_add_structure(test_structure, internal_node, ts);
- *
- * if (!ast_data_search_match(search, internal_node)) {
- * ast_data_remove_node(root_node, internal_node);
- * }
- *
- * return 0;
- * }
- *
- * \endcode
- *
- * \subsection Get
- *
- * \b Getting \b the \b tree
- *
- * To get the tree you need to create a query, a query is based on three parameters
- * a \b path to the provider, a \b search condition and a \b filter condition.
- * \code
- * struct ast_data *result;
- * struct ast_data_query query = {
- * .path = "/asterisk/application/app_queue/queues",
- * .search = "/queues/queue/name=queue1",
- * .filter = "/queues/queue/name|wrapuptime|members/member/interface"
- * };
- *
- * result = ast_data_get(&query);
- * \endcode
- *
- * After using it you need to release the allocated memory of the returned tree:
- * \code
- * ast_data_free(result);
- * \endcode
- *
- * \b Iterate
- *
- * To retrieve nodes from the tree, it is possible to iterate through the returned
- * nodes of the tree using:
- * \code
- * struct ast_data_iterator *i;
- * struct ast_data *internal_node;
- *
- * i = ast_data_iterator_init(result_tree, "path/node_name");
- * while ((internal_node = ast_data_iterator_next(i))) {
- * ... do something with node ...
- * }
- * ast_data_iterator_end(i);
- * \endcode
- * node_name is the name of the nodes to retrieve and path is the path to the internal
- * nodes to retrieve (if needed).
- *
- * \b Retrieving
- *
- * After getting the node you where searching for, you will need to retrieve its value,
- * to do that you may use one of the ast_data_retrieve_##type functions:
- * \code
- * int a = ast_data_retrieve_int(tree, "path/to/the/node");
- * double b = ast_data_retrieve_dbl(tree, "path/to/the/node");
- * unsigned int c = ast_data_retrieve_bool(tree, "path/to/the/node");
- * char *d = ast_data_retrieve_string(tree, "path/to/the/node");
- * struct sockaddr_in e = ast_data_retrieve_ipaddr(tree, "path/to/the/node");
- * unsigned int f = ast_data_retrieve_uint(tree, "path/to/the/node");
- * void *g = ast_data_retrieve_ptr(tree, "path/to/the/node");
- * \endcode
- *
- */
-
-#if defined(__cplusplus) || defined(c_plusplus)
-extern "C" {
-#endif
-
-/*! \brief The data type of the data node. */
-enum ast_data_type {
- AST_DATA_CONTAINER,
- AST_DATA_INTEGER,
- AST_DATA_UNSIGNED_INTEGER,
- AST_DATA_DOUBLE,
- AST_DATA_BOOLEAN,
- AST_DATA_STRING,
- AST_DATA_CHARACTER,
- AST_DATA_PASSWORD,
- AST_DATA_IPADDR,
- AST_DATA_TIMESTAMP,
- AST_DATA_SECONDS,
- AST_DATA_MILLISECONDS,
- AST_DATA_POINTER
-};
-
-/*! \brief The Data API structures version. */
-#define AST_DATA_HANDLER_VERSION 1
-#define AST_DATA_QUERY_VERSION 1
-
-/*! \brief opaque definition of an ast_data handler, a tree node. */
-struct ast_data;
-
-/*! \brief opaque definition of an ast_data_iterator handler. */
-struct ast_data_iterator;
-
-/*! \brief opaque definition of an ast_data_search structure. */
-struct ast_data_search;
-
-/*! \brief structure retrieved from a node, with the nodes content. */
-struct ast_data_retrieve {
- /*! \brief The type of the node retrieved. */
- enum ast_data_type type;
-
- union {
- char AST_DATA_CHARACTER;
- char *AST_DATA_STRING;
- char *AST_DATA_PASSWORD;
- int AST_DATA_INTEGER;
- unsigned int AST_DATA_TIMESTAMP;
- unsigned int AST_DATA_SECONDS;
- unsigned int AST_DATA_MILLISECONDS;
- double AST_DATA_DOUBLE;
- unsigned int AST_DATA_UNSIGNED_INTEGER;
- unsigned int AST_DATA_BOOLEAN;
- void *AST_DATA_POINTER;
- struct in_addr AST_DATA_IPADDR;
- void *AST_DATA_CONTAINER;
- } value;
-};
-
-/*!
- * \brief The get callback definition.
- */
-typedef int (*ast_data_get_cb)(const struct ast_data_search *search,
- struct ast_data *root);
-
-/*! \brief The structure of the node handler. */
-struct ast_data_handler {
- /*! \brief Structure version. */
- uint32_t version;
- /*! \brief Data get callback implementation. */
- ast_data_get_cb get;
-};
-
-/*! \brief This entries are for multiple registers. */
-struct ast_data_entry {
- /*! \brief Path of the node to register. */
- const char *path;
- /*! \brief Data handler structure. */
- const struct ast_data_handler *handler;
-};
-
-#define AST_DATA_ENTRY(__path, __handler) { .path = __path, .handler = __handler }
-
-/*! \brief A query to the data API is specified in this structure. */
-struct ast_data_query {
- /*! \brief Data query version. */
- uint32_t version;
- /*! \brief Path to the node to retrieve. */
- char *path;
- /*! \brief Filter string, return the internal nodes specified here.
- * Setting it to NULL will return every internal node. */
- char *filter;
- /*! \brief Search condition. */
- char *search;
-};
-
-/*! \brief Map the members of a structure. */
-struct ast_data_mapping_structure {
- /*! \brief structure member name. */
- const char *name;
- /*! \brief structure member type. */
- enum ast_data_type type;
- /*! \brief member getter. */
- union {
- char (*AST_DATA_CHARACTER)(void *ptr);
- char *(*AST_DATA_STRING)(void *ptr);
- char *(*AST_DATA_PASSWORD)(void *ptr);
- int (*AST_DATA_INTEGER)(void *ptr);
- int (*AST_DATA_TIMESTAMP)(void *ptr);
- int (*AST_DATA_SECONDS)(void *ptr);
- int (*AST_DATA_MILLISECONDS)(void *ptr);
- double (*AST_DATA_DOUBLE)(void *ptr);
- unsigned int (*AST_DATA_UNSIGNED_INTEGER)(void *ptr);
- unsigned int (*AST_DATA_BOOLEAN)(void *ptr);
- void *(*AST_DATA_POINTER)(void *ptr);
- struct in_addr (*AST_DATA_IPADDR)(void *ptr);
- void *(*AST_DATA_CONTAINER)(void *ptr);
- } get;
-};
-
-/* Generate the structure and the functions to access the members of a structure. */
-#define AST_DATA_STRUCTURE(__struct, __name) \
- __name(__AST_DATA_MAPPING_FUNCTION); \
- static const struct ast_data_mapping_structure __data_mapping_structure_##__struct[] = { \
- __name(__AST_DATA_MAPPING_STRUCTURE) \
- }
-
-/* Generate the structure to access the members and setup the pointer of the getter. */
-#define __AST_DATA_MAPPING_STRUCTURE(__structure, __member, __type) \
- { .name = #__member, .get.__type = data_mapping_structure_get_##__structure##__member, \
- .type = __type },
-
-/* based on the data type, specifify the type of return value for the getter function. */
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_PASSWORD(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_PASSWORD, char *)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_STRING(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_STRING, char *)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_CHARACTER(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_CHARACTER, char)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_INTEGER(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_INTEGER, int)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_TIMESTAMP(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_INTEGER, int)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_SECONDS(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_INTEGER, int)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_MILLISECONDS(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_INTEGER, int)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_UNSIGNED_INTEGER(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_UNSIGNED_INTEGER, unsigned int)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_BOOLEAN(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_BOOLEAN, unsigned int)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_POINTER(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_POINTER, void *)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_IPADDR(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_IPADDR, struct in_addr)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_DOUBLE(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_DBL, double)
-#define __AST_DATA_MAPPING_FUNCTION_AST_DATA_CONTAINER(__structure, __member) \
- __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, AST_DATA_CONTAINER, void *)
-
-#define __AST_DATA_MAPPING_FUNCTION(__structure, __member, __type) \
- __AST_DATA_MAPPING_FUNCTION_##__type(__structure, __member)
-
-/* Create the function to retrieve a member of the structure. */
-#define __AST_DATA_MAPPING_FUNCTION_TYPE(__structure, __member, __type, __real_type) \
- static __real_type data_mapping_structure_get_##__structure##__member(void *ptr) { \
- struct __structure *struct_##__member = (struct __structure *) ptr; \
- return (__real_type) struct_##__member->__member; \
- }
-
-/*!
- * \brief Register a data provider.
- * \param[in] path The path of the node to register.
- * \param[in] handler The structure defining this node handler.
- * \param[in] registrar Who is registering this node.
- * \param[in] mod The module registering this handler.
- * \see ast_data_unregister
- * \retval <0 on error.
- * \retval 0 on success.
- * \see __ast_data_unregister, __ast_data_register_multiple
- */
-int __ast_data_register(const char *path, const struct ast_data_handler *handler,
- const char *registrar, struct ast_module *mod);
-#define ast_data_register(path, handler) __ast_data_register(path, handler, __FILE__, AST_MODULE_SELF)
-#define ast_data_register_core(path, handler) __ast_data_register(path, handler, __FILE__, NULL)
-
-/*!
- * \brief Register multiple data providers at once.
- * \param[in] data_entries An array of data_entries structures.
- * \param[in] entries The number of entries in the data_entries array.
- * \param[in] registrar Who is registering this nodes.
- * \param[in] mod The module registering this handlers.
- * \retval <0 on error (none of the nodes are being registered on error).
- * \retval 0 on success.
- * \see __ast_data_register, __ast_data_unregister
- */
-int __ast_data_register_multiple(const struct ast_data_entry *data_entries,
- size_t entries, const char *registrar, struct ast_module *mod);
-#define ast_data_register_multiple(data_entries, entries) \
- __ast_data_register_multiple(data_entries, entries, __FILE__, AST_MODULE_SELF)
-#define ast_data_register_multiple_core(data_entries, entries) \
- __ast_data_register_multiple(data_entries, entries, __FILE__, NULL)
-
-/*!
- * \brief Unregister a data provider.
- * \param[in] path Which node to unregister, if path is NULL unregister every node
- * registered by the passed 'registrar'.
- * \param[in] registrar Who is trying to unregister this node, only the owner (the
- * one who registered the node) will be able to unregister it.
- * \see ast_data_register
- * \retval <0 on error.
- * \retval 0 on success.
- * \see __ast_data_register, __ast_data_register_multiple
- */
-int __ast_data_unregister(const char *path, const char *registrar);
-#define ast_data_unregister(path) __ast_data_unregister(path, __FILE__)
-
-/*!
- * \brief Check the current generated node to know if it matches the search
- * condition.
- * \param[in] search The search condition.
- * \param[in] data The AstData node generated.
- * \return 1 If the "data" node matches the search condition.
- * \return 0 If the "data" node does not matches the search condition.
- * \see ast_data_remove_node
- */
-int ast_data_search_match(const struct ast_data_search *search, struct ast_data *data);
-
-/*!
- * \brief Based on a search tree, evaluate every member of a structure against it.
- * \param[in] search The search tree.
- * \param[in] mapping The structure mapping.
- * \param[in] mapping_len The lenght of the structure mapping.
- * \param[in] structure The structure pointer.
- * \param[in] structure_name The name of the structure to compare.
- * \retval 0 If the structure matches.
- * \retval 1 If the structure doesn't match.
- */
-int __ast_data_search_cmp_structure(const struct ast_data_search *search,
- const struct ast_data_mapping_structure *mapping, size_t mapping_len,
- void *structure, const char *structure_name);
-#define ast_data_search_cmp_structure(search, structure_name, structure, structure_name_cmp) \
- __ast_data_search_cmp_structure(search, __data_mapping_structure_##structure_name, \
- ARRAY_LEN(__data_mapping_structure_##structure_name), structure, structure_name_cmp)
-
-/*!
- * \brief Retrieve a subtree from the asterisk data API.
- * \param[in] query The query structure specifying what nodes to retrieve.
- * \retval NULL on error.
- * \retval non-NULL The dynamically allocated requested sub-tree (it needs to be
- * released using ast_data_free.
- * \see ast_data_free, ast_data_get_xml
- */
-struct ast_data *ast_data_get(const struct ast_data_query *query);
-
-#ifdef HAVE_LIBXML2
-/*!
- * \brief Retrieve a subtree from the asterisk data API in XML format..
- * \param[in] query The query structure specifying what nodes to retrieve.
- * \retval NULL on error.
- * \retval non-NULL The dynamically allocated requested sub-tree (it needs to be
- * released using ast_data_free.
- * \see ast_data_free, ast_data_get
- */
-struct ast_xml_doc *ast_data_get_xml(const struct ast_data_query *query);
-#endif
-
-/*!
- * \brief Release the allocated memory of a tree.
- * \param[in] root The sub-tree pointer returned by a call to ast_data_get.
- * \see ast_data_get
- */
-void ast_data_free(struct ast_data *root);
-
-/*!
- * \brief Get a node type.
- * \param[in] res A pointer to the ast_data result set.
- * \param[in] path A path to the node to get the type.
- * \return The type of the requested node type.
- */
-enum ast_data_type ast_data_retrieve_type(struct ast_data *res, const char *path);
-
-/*!
- * \brief Get the node name.
- * \param[in] node The node pointer.
- * \returns The node name.
- */
-char *ast_data_retrieve_name(struct ast_data *node);
-
-/*!
- * \brief Add a container child.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_node(struct ast_data *root, const char *childname);
-
-/*!
- * \brief Add an integer node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] value The value for the new node.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_int(struct ast_data *root, const char *childname,
- int value);
-
-/*!
- * \brief Add a char node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] value The value for the new node.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_char(struct ast_data *root, const char *childname,
- char value);
-
-/*!
- * \brief Add an unsigned integer node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] value The value for the new node.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_uint(struct ast_data *root, const char *childname,
- unsigned int value);
-
-/*!
- * \brief Add a floating point node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] dbl The value for the new node.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_dbl(struct ast_data *root, const char *childname,
- double dbl);
-/*!
- * \brief Add a ipv4 address type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] addr The ipv4 address value.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_ipaddr(struct ast_data *root, const char *childname,
- struct in_addr addr);
-
-/*!
- * \brief Add a ptr node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] ptr The pointer value to add.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_ptr(struct ast_data *root, const char *childname,
- void *ptr);
-
-/*!
- * \brief Add a password node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] string The value for the new node.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_password(struct ast_data *root, const char *childname,
- const char *string);
-
-/*!
- * \brief Add a timestamp node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] timestamp The value for the new node.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_timestamp(struct ast_data *root, const char *childname,
- unsigned int timestamp);
-
-/*!
- * \brief Add a seconds node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] seconds The value for the new node.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_seconds(struct ast_data *root, const char *childname,
- unsigned int seconds);
-
-/*!
- * \brief Add a milliseconds node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] milliseconds The value for the new node.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_milliseconds(struct ast_data *root, const char *childname,
- unsigned int milliseconds);
-
-/*!
- * \brief Add a string node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] string The value for the new node.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_str(struct ast_data *root, const char *childname,
- const char *string);
-
-/*!
- * \brief Add a boolean node type.
- * \param[in] root The root of the ast_data to insert into.
- * \param[in] childname The name of the child element to be added.
- * \param[in] boolean The value for the new node.
- * \retval NULL on error (memory exhaustion only).
- * \retval non-NULL a newly allocated node.
- */
-struct ast_data *ast_data_add_bool(struct ast_data *root, const char *childname,
- unsigned int boolean);
-
-/*!
- * \brief Add a complete structure to a node.
- * \param[in] root Where to add the structure.
- * \param[in] mapping The structure mapping array.
- * \param[in] mapping_len The lenght of the mapping array.
- * \param[in] structure The structure pointer.
- * \retval 0 on success.
- * \retval 1 on error.
- */
-int __ast_data_add_structure(struct ast_data *root,
- const struct ast_data_mapping_structure *mapping,
- size_t mapping_len, void *structure);
-#define ast_data_add_structure(structure_name, root, structure) \
- __ast_data_add_structure(root, __data_mapping_structure_##structure_name, \
- ARRAY_LEN(__data_mapping_structure_##structure_name), structure)
-
-/*!
- * \brief Remove a node that was added using ast_data_add_
- * \param[in] root The root node of the node to be removed.
- * \param[in] child The node pointer to remove.
- */
-void ast_data_remove_node(struct ast_data *root, struct ast_data *child);
-
-/*!
- * \brief Initialize an iterator.
- * \param[in] tree The returned tree by a call to ast_data_get.
- * \param[in] elements Which elements to iterate through.
- * \retval NULL on error.
- * \retval non-NULL A dinamically allocated iterator structure.
- */
-struct ast_data_iterator *ast_data_iterator_init(struct ast_data *tree,
- const char *elements);
-
-/*!
- * \brief Release (stop using) an iterator.
- * \param[in] iterator The iterator created by ast_data_iterator_start.
- * \see ast_data_iterator_start
- */
-void ast_data_iterator_end(struct ast_data_iterator *iterator);
-
-/*!
- * \brief Get the next node of the tree.
- * \param[in] iterator The iterator structure returned by ast_data_iterator_start.
- * \retval NULL when no more nodes to return.
- * \retval non-NULL A node of the ast_data tree.
- * \see ast_data_iterator_start, ast_data_iterator_stop
- */
-struct ast_data *ast_data_iterator_next(struct ast_data_iterator *iterator);
-
-/*!
- * \brief Retrieve a value from a node in the tree.
- * \param[in] tree The structure returned by a call to ast_data_get.
- * \param[in] path The path to the node.
- * \param[out] content The node content.
- * \retval 0 on success.
- * \retval <0 on error.
- */
-int ast_data_retrieve(struct ast_data *tree, const char *path, struct ast_data_retrieve *content);
-
-/*!
- * \brief Retrieve the integer value of a node.
- * \param[in] tree The tree from where to get the value.
- * \param[in] path The node name or path.
- * \returns The value of the node.
- */
-static inline int ast_data_retrieve_int(struct ast_data *tree, const char *path)
-{
- struct ast_data_retrieve ret;
-
- ast_data_retrieve(tree, path, &ret);
-
- return ret.value.AST_DATA_INTEGER;
-}
-
-/*!
- * \brief Retrieve the character value of a node.
- * \param[in] tree The tree from where to get the value.
- * \param[in] path The node name or path.
- * \returns The value of the node.
- */
-static inline char ast_data_retrieve_char(struct ast_data *tree, const char *path)
-{
- struct ast_data_retrieve ret;
-
- ast_data_retrieve(tree, path, &ret);
-
- return ret.value.AST_DATA_CHARACTER;
-}
-
-/*!
- * \brief Retrieve the boolean value of a node.
- * \param[in] tree The tree from where to get the value.
- * \param[in] path The node name or path.
- * \returns The value of the node.
- */
-static inline unsigned int ast_data_retrieve_bool(struct ast_data *tree, const char *path)
-{
- struct ast_data_retrieve ret;
-
- ast_data_retrieve(tree, path, &ret);
-
- return ret.value.AST_DATA_BOOLEAN;
-}
-
-/*!
- * \brief Retrieve the unsigned integer value of a node.
- * \param[in] tree The tree from where to get the value.
- * \param[in] path The node name or path.
- * \returns The value of the node.
- */
-static inline unsigned int ast_data_retrieve_uint(struct ast_data *tree, const char *path)
-{
- struct ast_data_retrieve ret;
-
- ast_data_retrieve(tree, path, &ret);
-
- return ret.value.AST_DATA_UNSIGNED_INTEGER;
-}
-
-/*!
- * \brief Retrieve the password value of a node.
- * \param[in] tree The tree from where to get the value.
- * \param[in] path The node name or path.
- * \returns The value of the node.
- */
-static inline const char *ast_data_retrieve_password(struct ast_data *tree, const char *path)
-{
- struct ast_data_retrieve ret;
-
- ast_data_retrieve(tree, path, &ret);
-
- return ret.value.AST_DATA_PASSWORD;
-}
-
-/*!
- * \brief Retrieve the string value of a node.
- * \param[in] tree The tree from where to get the value.
- * \param[in] path The node name or path.
- * \returns The value of the node.
- */
-static inline const char *ast_data_retrieve_string(struct ast_data *tree, const char *path)
-{
- struct ast_data_retrieve ret;
-
- ast_data_retrieve(tree, path, &ret);
-
- return ret.value.AST_DATA_STRING;
-}
-
-/*!
- * \brief Retrieve the ptr value of a node.
- * \param[in] tree The tree from where to get the value.
- * \param[in] path The node name or path.
- * \returns The value of the node.
- */
-static inline void *ast_data_retrieve_ptr(struct ast_data *tree, const char *path)
-{
- struct ast_data_retrieve ret;
-
- ast_data_retrieve(tree, path, &ret);
-
- return ret.value.AST_DATA_POINTER;
-}
-
-/*!
- * \brief Retrieve the double value of a node.
- * \param[in] tree The tree from where to get the value.
- * \param[in] path The node name or path.
- * \returns The value of the node.
- */
-static inline double ast_data_retrieve_dbl(struct ast_data *tree, const char *path)
-{
- struct ast_data_retrieve ret;
-
- ast_data_retrieve(tree, path, &ret);
-
- return ret.value.AST_DATA_DOUBLE;
-}
-
-/*!
- * \brief Retrieve the ipv4 address value of a node.
- * \param[in] tree The tree from where to get the value.
- * \param[in] path The node name or path.
- * \returns The value of the node.
- */
-static inline struct in_addr ast_data_retrieve_ipaddr(struct ast_data *tree, const char *path)
-{
- struct ast_data_retrieve ret;
-
- ast_data_retrieve(tree, path, &ret);
-
- return ret.value.AST_DATA_IPADDR;
-}
-
-/*!
- * \brief Add the codec in the root node based on the format parameter.
- * \param[in] root The astdata root node where to add the codec node.
- * \param[in] node_name The name of the node where we are going to add the codec.
- * \param[in] format The codec allowed.
- * \return < 0 on error.
- * \return 0 on success.
- */
-int ast_data_add_codec(struct ast_data *root, const char *node_name, struct ast_format *format);
-
-/*!
- * \brief Add the list of codecs in the root node based on the capability parameter.
- * \param[in] root The astdata root node where to add the codecs node.
- * \param[in] node_name The name of the node where we are going to add the list of
- * codecs.
- * \param[in] capability The codecs allowed.
- * \return < 0 on error.
- * \return 0 on success.
- */
-int ast_data_add_codecs(struct ast_data *root, const char *node_name, struct ast_format_cap *capability);
-
-#if defined(__cplusplus) || defined(c_plusplus)
-}
-#endif
-
-#endif /* ASTERISK_DATA_H */
diff --git a/include/asterisk/format.h b/include/asterisk/format.h
index 0bad96dcc..946c03d98 100644
--- a/include/asterisk/format.h
+++ b/include/asterisk/format.h
@@ -32,7 +32,7 @@ struct ast_format;
/*! \brief Format comparison results */
enum ast_format_cmp_res {
- /*! Both formats are equivalent to eachother */
+ /*! Both formats are equivalent to each other */
AST_FORMAT_CMP_EQUAL = 0,
/*! Both formats are completely different and not the same in any way */
AST_FORMAT_CMP_NOT_EQUAL,
@@ -110,7 +110,7 @@ struct ast_format_interface {
struct ast_format *(* const format_parse_sdp_fmtp)(const struct ast_format *format, const char *attributes);
/*!
- * \brief Generate SDP attribute information from an ast_format_attr structure.
+ * \brief Generate SDP attribute information from an ast_format structure.
*
* \param format The format containing attributes
* \param payload The payload number to place into the fmtp line
diff --git a/include/asterisk/format_cache.h b/include/asterisk/format_cache.h
index 92272e8eb..067546310 100644
--- a/include/asterisk/format_cache.h
+++ b/include/asterisk/format_cache.h
@@ -184,6 +184,11 @@ extern struct ast_format *ast_format_mp4;
extern struct ast_format *ast_format_vp8;
/*!
+ * \brief Built-in cached vp9 format.
+ */
+extern struct ast_format *ast_format_vp9;
+
+/*!
* \brief Built-in cached jpeg format.
*/
extern struct ast_format *ast_format_jpeg;
diff --git a/include/asterisk/frame.h b/include/asterisk/frame.h
index 2f6c365ad..8f0daccb7 100644
--- a/include/asterisk/frame.h
+++ b/include/asterisk/frame.h
@@ -137,6 +137,8 @@ enum {
AST_FRFLAG_HAS_TIMING_INFO = (1 << 0),
/*! This frame has been requeued */
AST_FRFLAG_REQUEUED = (1 << 1),
+ /*! This frame contains a valid sequence number */
+ AST_FRFLAG_HAS_SEQUENCE_NUMBER = (1 << 2),
};
struct ast_frame_subclass {
diff --git a/include/asterisk/indications.h b/include/asterisk/indications.h
index b02be1fdf..309954b05 100644
--- a/include/asterisk/indications.h
+++ b/include/asterisk/indications.h
@@ -28,7 +28,6 @@
#include "asterisk/astobj2.h"
#include "asterisk/utils.h"
-#include "asterisk/data.h"
/*!
* \brief Description of a tone
@@ -242,12 +241,4 @@ static inline struct ast_tone_zone_sound *ast_tone_zone_sound_ref(struct ast_ton
return ts;
}
-/*!
- * \brief Add a tone_zone structure to the data tree specified.
- *
- * \retval <0 on error.
- * \retval 0 on success.
- */
-int ast_tone_zone_data_add_structure(struct ast_data *tree, struct ast_tone_zone *zone);
-
#endif /* _ASTERISK_INDICATIONS_H */
diff --git a/include/asterisk/manager.h b/include/asterisk/manager.h
index 3de7b1d17..67dcead47 100644
--- a/include/asterisk/manager.h
+++ b/include/asterisk/manager.h
@@ -54,7 +54,7 @@
- \ref manager.c Main manager code file
*/
-#define AMI_VERSION "3.2.0"
+#define AMI_VERSION "4.0.0"
#define DEFAULT_MANAGER_PORT 5038 /* Default port for Asterisk management via TCP */
#define DEFAULT_MANAGER_TLS_PORT 5039 /* Default port for Asterisk management via TCP */
diff --git a/include/asterisk/res_pjsip.h b/include/asterisk/res_pjsip.h
index 8c589ef85..cf366cbab 100644
--- a/include/asterisk/res_pjsip.h
+++ b/include/asterisk/res_pjsip.h
@@ -365,6 +365,8 @@ enum ast_sip_dtmf_mode {
AST_SIP_DTMF_INFO,
/*! Use SIP 4733 if supported by the other side or INBAND if not */
AST_SIP_DTMF_AUTO,
+ /*! Use SIP 4733 if supported by the other side or INFO DTMF (blech) if not */
+ AST_SIP_DTMF_AUTO_INFO,
};
/*!
@@ -666,6 +668,8 @@ struct ast_sip_endpoint_media_configuration {
struct ast_sip_t38_configuration t38;
/*! Configured codecs */
struct ast_format_cap *codecs;
+ /*! Capabilities in topology form */
+ struct ast_stream_topology *topology;
/*! DSCP TOS bits for audio streams */
unsigned int tos_audio;
/*! Priority for audio streams */
@@ -680,6 +684,14 @@ struct ast_sip_endpoint_media_configuration {
unsigned int bind_rtp_to_media_address;
/*! Use RTCP-MUX */
unsigned int rtcp_mux;
+ /*! Maximum number of audio streams to offer/accept */
+ unsigned int max_audio_streams;
+ /*! Maximum number of video streams to offer/accept */
+ unsigned int max_video_streams;
+ /*! Use BUNDLE */
+ unsigned int bundle;
+ /*! Enable webrtc settings and defaults */
+ unsigned int webrtc;
};
/*!
@@ -769,6 +781,8 @@ struct ast_sip_endpoint {
unsigned int allow_overlap;
/*! Whether to notifies all the progress details on blind transfer */
unsigned int refer_blind_progress;
+ /*! Whether to notifies dialog-info 'early' on INUSE && RINGING state */
+ unsigned int notify_early_inuse_ringing;
};
/*! URI parameter for symmetric transport */
@@ -2049,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
diff --git a/include/asterisk/res_pjsip_presence_xml.h b/include/asterisk/res_pjsip_presence_xml.h
index deed0901e..55b79ad6e 100644
--- a/include/asterisk/res_pjsip_presence_xml.h
+++ b/include/asterisk/res_pjsip_presence_xml.h
@@ -69,7 +69,8 @@ void ast_sip_sanitize_xml(const char *input, char *output, size_t len);
* \param[out] local_state
*/
void ast_sip_presence_exten_state_to_str(int state, char **statestring,
- char **pidfstate, char **pidfnote, enum ast_sip_pidf_state *local_state);
+ char **pidfstate, char **pidfnote, enum ast_sip_pidf_state *local_state,
+ unsigned int notify_early_inuse_ringing);
/*!
* \brief Create XML attribute
diff --git a/include/asterisk/res_pjsip_session.h b/include/asterisk/res_pjsip_session.h
index e2a90662e..eae11af43 100644
--- a/include/asterisk/res_pjsip_session.h
+++ b/include/asterisk/res_pjsip_session.h
@@ -28,6 +28,8 @@
#include "asterisk/netsock2.h"
/* Needed for ast_sdp_srtp struct */
#include "asterisk/sdp_srtp.h"
+/* Needed for ast_media_type */
+#include "asterisk/codec.h"
/* Forward declarations */
struct ast_sip_endpoint;
@@ -56,17 +58,21 @@ enum ast_sip_session_t38state {
};
struct ast_sip_session_sdp_handler;
+struct ast_sip_session;
+struct ast_sip_session_media;
+
+typedef struct ast_frame *(*ast_sip_session_media_read_cb)(struct ast_sip_session *session, struct ast_sip_session_media *session_media);
+typedef int (*ast_sip_session_media_write_cb)(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ struct ast_frame *frame);
/*!
* \brief A structure containing SIP session media information
*/
struct ast_sip_session_media {
- union {
- /*! \brief RTP instance itself */
- struct ast_rtp_instance *rtp;
- /*! \brief UDPTL instance itself */
- struct ast_udptl *udptl;
- };
+ /*! \brief RTP instance itself */
+ struct ast_rtp_instance *rtp;
+ /*! \brief UDPTL instance itself */
+ struct ast_udptl *udptl;
/*! \brief Direct media address */
struct ast_sockaddr direct_media_addr;
/*! \brief SDP handler that setup the RTP */
@@ -87,8 +93,46 @@ struct ast_sip_session_media {
unsigned int locally_held:1;
/*! \brief Does remote support rtcp_mux */
unsigned int remote_rtcp_mux:1;
- /*! \brief Stream type this session media handles */
- char stream_type[1];
+ /*! \brief Media type of this session media */
+ enum ast_media_type type;
+ /*! \brief The write callback when writing frames */
+ ast_sip_session_media_write_cb write_callback;
+ /*! \brief The stream number to place into any resulting frames */
+ int stream_num;
+ /*! \brief Media identifier for this stream (may be shared across multiple streams) */
+ char *mid;
+ /*! \brief The bundle group the stream belongs to */
+ int bundle_group;
+ /*! \brief Whether this stream is currently bundled or not */
+ unsigned int bundled;
+ /*! \brief RTP/Media streams association identifier */
+ char *msid;
+};
+
+/*!
+ * \brief Structure which contains read callback information
+ */
+struct ast_sip_session_media_read_callback_state {
+ /*! \brief The file descriptor itself */
+ int fd;
+ /*! \brief The callback to invoke */
+ ast_sip_session_media_read_cb read_callback;
+ /*! \brief The media session */
+ struct ast_sip_session_media *session;
+};
+
+/*!
+ * \brief Structure which contains media state information (streams, sessions)
+ */
+struct ast_sip_session_media_state {
+ /*! \brief Mapping of stream to media sessions */
+ AST_VECTOR(, struct ast_sip_session_media *) sessions;
+ /*! \brief Added read callbacks - these are whole structs and not pointers */
+ AST_VECTOR(, struct ast_sip_session_media_read_callback_state) read_callbacks;
+ /*! \brief Default media sessions for each type */
+ struct ast_sip_session_media *default_session[AST_MEDIA_TYPE_END];
+ /*! \brief The media stream topology */
+ struct ast_stream_topology *topology;
};
/*!
@@ -123,8 +167,6 @@ struct ast_sip_session {
AST_LIST_HEAD(, ast_sip_session_supplement) supplements;
/*! Datastores added to the session by supplements to the session */
struct ao2_container *datastores;
- /*! Media streams */
- struct ao2_container *media;
/*! Serializer for tasks relating to this SIP session */
struct ast_taskprocessor *serializer;
/*! Non-null if the session serializer is suspended or being suspended. */
@@ -139,8 +181,10 @@ struct ast_sip_session {
pj_timer_entry scheduled_termination;
/*! Identity of endpoint this session deals with */
struct ast_party_id id;
- /*! Requested capabilities */
- struct ast_format_cap *req_caps;
+ /*! Active media state (sessions + streams) - contents are guaranteed not to change */
+ struct ast_sip_session_media_state *active_media_state;
+ /*! Pending media state (sessions + streams) */
+ struct ast_sip_session_media_state *pending_media_state;
/*! Optional DSP, used only for inband DTMF/Fax-CNG detection if configured */
struct ast_dsp *dsp;
/*! Whether the termination of the session should be deferred */
@@ -315,34 +359,29 @@ struct ast_sip_session_sdp_handler {
/*!
* \brief Set session details based on a stream in an incoming SDP offer or answer
* \param session The session for which the media is being negotiated
- * \param session_media The media to be setup for this session
+ * \param session_media The media session
* \param sdp The entire SDP. Useful for getting "global" information, such as connections or attributes
- * \param stream The stream on which to operate
+ * \param index The index for the session media, Asterisk stream, and PJMEDIA stream being negotiated
+ * \param asterisk_stream The Asterisk stream representation
* \retval 0 The stream was not handled by this handler. If there are other registered handlers for this stream type, they will be called.
* \retval <0 There was an error encountered. No further operation will take place and the current negotiation will be abandoned.
* \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.
*/
- int (*negotiate_incoming_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream);
- /*!
- * \brief Create an SDP media stream and add it to the outgoing SDP offer or answer
- * \param session The session for which media is being added
- * \param session_media The media to be setup for this session
- * \param stream The stream on which to operate
- * \retval 0 The stream was not handled by this handler. If there are other registered handlers for this stream type, they will be called.
- * \retval <0 There was an error encountered. No further operation will take place and the current negotiation will be abandoned.
- * \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.
- */
- int (*handle_incoming_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp, struct pjmedia_sdp_media *stream);
+ int (*negotiate_incoming_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_session *sdp, int index, struct ast_stream *asterisk_stream);
/*!
* \brief Create an SDP media stream and add it to the outgoing SDP offer or answer
* \param session The session for which media is being added
* \param session_media The media to be setup for this session
* \param sdp The entire SDP as currently built
+ * \param remote Optional remote SDP if this is an answer
+ * \param stream The stream that is to be added to the outgoing SDP
* \retval 0 This handler has no stream to add. If there are other registered handlers for this stream type, they will be called.
* \retval <0 There was an error encountered. No further operation will take place and the current SDP negotiation will be abandoned.
* \retval >0 The handler has a stream to be added to the SDP. No further handler of this stream type will be called.
*/
- int (*create_outgoing_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp);
+ int (*create_outgoing_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct pjmedia_sdp_session *sdp,
+ const struct pjmedia_sdp_session *remote, struct ast_stream *stream);
/*!
* \brief Update media stream with external address if applicable
* \param tdata The outgoing message itself
@@ -353,17 +392,18 @@ struct ast_sip_session_sdp_handler {
/*!
* \brief Apply a negotiated SDP media stream
* \param session The session for which media is being applied
- * \param session_media The media to be setup for this session
+ * \param session_media The media session
* \param local The entire local negotiated SDP
- * \param local_stream The local stream which to apply
* \param remote The entire remote negotiated SDP
- * \param remote_stream The remote stream which to apply
+ * \param index The index of the session media, SDP streams, and Asterisk streams
+ * \param asterisk_stream The Asterisk stream representation
* \retval 0 The stream was not applied by this handler. If there are other registered handlers for this stream type, they will be called.
* \retval <0 There was an error encountered. No further operation will take place and the current application will be abandoned.
* \retval >0 The stream was handled by this handler. No further handler of this stream type will be called.
*/
- int (*apply_negotiated_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
- const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream);
+ int (*apply_negotiated_sdp_stream)(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_session *remote, int index,
+ struct ast_stream *asterisk_stream);
/*!
* \brief Stop a session_media created by this handler but do not destroy resources
* \param session The session for which media is being stopped
@@ -393,7 +433,7 @@ struct ast_sip_channel_pvt {
/*!
* \brief Allocate a new SIP channel pvt structure
*
- * \param pvt Pointer to channel specific implementation
+ * \param pvt Pointer to channel specific information
* \param session Pointer to SIP session
*
* \retval non-NULL success
@@ -452,11 +492,11 @@ void ast_sip_session_unsuspend(struct ast_sip_session *session);
* \param contact The contact that this session will communicate with
* \param location Name of the location to call, be it named location or explicit URI. Overrides contact if present.
* \param request_user Optional request user to place in the request URI if permitted
- * \param req_caps The requested capabilities
+ * \param req_topology The requested capabilities
*/
struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint,
struct ast_sip_contact *contact, const char *location, const char *request_user,
- struct ast_format_cap *req_caps);
+ struct ast_stream_topology *req_topology);
/*!
* \brief Terminate a session and, if possible, send the provided response code
@@ -613,15 +653,20 @@ void ast_sip_session_remove_datastore(struct ast_sip_session *session, const cha
* \param on_response Callback called when response for request is received
* \param method The method that should be used when constructing the session refresh
* \param generate_new_sdp Boolean to indicate if a new SDP should be created
+ * \param media_state Optional requested media state for the SDP
+ *
* \retval 0 Successfully sent refresh
* \retval -1 Failure to send refresh
+ *
+ * \note If a media_state is passed in ownership will be taken in all cases
*/
int ast_sip_session_refresh(struct ast_sip_session *session,
ast_sip_session_request_creation_cb on_request_creation,
ast_sip_session_sdp_creation_cb on_sdp_creation,
ast_sip_session_response_cb on_response,
enum ast_sip_session_refresh_method method,
- int generate_new_sdp);
+ int generate_new_sdp,
+ struct ast_sip_session_media_state *media_state);
/*!
* \brief Send a SIP response
@@ -692,6 +737,123 @@ struct ast_sip_session *ast_sip_dialog_get_session(pjsip_dialog *dlg);
*/
void ast_sip_session_resume_reinvite(struct ast_sip_session *session);
+/*!
+ * \brief Determines if a provided pending stream will be the default stream or not
+ * \since 15.0.0
+ *
+ * \param session The session to check against
+ * \param stream The pending stream
+ *
+ * \retval 1 if stream will be default
+ * \retval 0 if stream will NOT be the default
+ */
+int ast_sip_session_is_pending_stream_default(const struct ast_sip_session *session, const struct ast_stream *stream);
+
+/*!
+ * \brief Allocate a session media state structure
+ * \since 15.0.0
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+struct ast_sip_session_media_state *ast_sip_session_media_state_alloc(void);
+
+/*!
+ * \brief Allocate an ast_session_media and add it to the media state's vector.
+ * \since 15.0.0
+ *
+ * This allocates a session media of the specified type. The position argument
+ * determines where in the vector that the new session media will be inserted.
+ *
+ * \note The returned ast_session_media is the reference held by the vector. Callers
+ * of this function must NOT decrement the refcount of the session media.
+ *
+ * \param session Session on which to query active media state for
+ * \param media_state Media state to place the session media into
+ * \param type The type of the session media
+ * \param position Position at which to insert the new session media.
+ *
+ * \note The active media state will be queried and if a media session already
+ * exists at the given position for the same type it will be reused instead of
+ * allocating a new one.
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_session *session,
+ struct ast_sip_session_media_state *media_state, enum ast_media_type type, int position);
+
+/*!
+ * \brief Reset a media state to a clean state
+ * \since 15.0.0
+ *
+ * \param media_state The media state to reset
+ */
+void ast_sip_session_media_state_reset(struct ast_sip_session_media_state *media_state);
+
+/*!
+ * \brief Clone a media state
+ * \since 15.0.0
+ *
+ * \param media_state The media state to clone
+ *
+ * \retval non-NULL success
+ * \retval NULL failure
+ */
+struct ast_sip_session_media_state *ast_sip_session_media_state_clone(const struct ast_sip_session_media_state *media_state);
+
+/*!
+ * \brief Free a session media state structure
+ * \since 15.0.0
+ */
+void ast_sip_session_media_state_free(struct ast_sip_session_media_state *media_state);
+
+/*!
+ * \brief Set a read callback for a media session with a specific file descriptor
+ * \since 15.0.0
+ *
+ * \param session The session
+ * \param session_media The media session
+ * \param fd The file descriptor
+ * \param callback The read callback
+ *
+ * \retval 0 the read callback was successfully added
+ * \retval -1 the read callback could not be added
+ *
+ * \note This operations on the pending media state
+ */
+int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ int fd, ast_sip_session_media_read_cb callback);
+
+/*!
+ * \brief Set a write callback for a media session
+ * \since 15.0.0
+ *
+ * \param session The session
+ * \param session_media The media session
+ * \param callback The write callback
+ *
+ * \retval 0 the write callback was successfully add
+ * \retval -1 the write callback is already set to something different
+ *
+ * \note This operates on the pending media state
+ */
+int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ ast_sip_session_media_write_cb callback);
+
+/*!
+ * \brief Retrieve the underlying media session that is acting as transport for a media session
+ * \since 15.0.0
+ *
+ * \param session The session
+ * \param session_media The media session to retrieve the transport for
+ *
+ * \note This operates on the pending media state
+ *
+ * \note This function is guaranteed to return non-NULL
+ */
+struct ast_sip_session_media *ast_sip_session_media_get_transport(struct ast_sip_session *session, struct ast_sip_session_media *session_media);
+
/*! \brief Determines whether the res_pjsip_session module is loaded */
#define CHECK_PJSIP_SESSION_MODULE_LOADED() \
do { \
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index 5f439163f..d030bdb19 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -603,6 +603,12 @@ struct ast_rtp_engine {
unsigned int (*ssrc_get)(struct ast_rtp_instance *instance);
/*! Callback to retrieve RTCP SDES CNAME */
const char *(*cname_get)(struct ast_rtp_instance *instance);
+ /*! Callback to bundle an RTP instance to another */
+ int (*bundle)(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);
+ /*! Callback to set remote SSRC information */
+ void (*set_remote_ssrc)(struct ast_rtp_instance *instance, unsigned int ssrc);
+ /*! Callback to set the stream identifier */
+ void (*set_stream_num)(struct ast_rtp_instance *instance, int stream_num);
/*! Callback to pointer for optional ICE support */
struct ast_rtp_engine_ice *ice;
/*! Callback to pointer for optional DTLS SRTP support */
@@ -640,12 +646,13 @@ struct ast_rtp_glue {
/*!
* \brief Used to prevent two channels from remotely bridging audio rtp if the channel tech has a
* reason for prohibiting it based on qualities that need to be compared from both channels.
- * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, function this is not used.
+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.
*/
int (*allow_rtp_remote)(struct ast_channel *chan1, struct ast_rtp_instance *instance);
/*!
* \brief Callback for retrieving the RTP instance carrying video
* \note This function increases the reference count on the returned RTP instance.
+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.
*/
enum ast_rtp_glue_result (*get_vrtp_info)(struct ast_channel *chan, struct ast_rtp_instance **instance);
/*!
@@ -658,11 +665,15 @@ struct ast_rtp_glue {
/*!
* \brief Callback for retrieving the RTP instance carrying text
* \note This function increases the reference count on the returned RTP instance.
+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.
*/
enum ast_rtp_glue_result (*get_trtp_info)(struct ast_channel *chan, struct ast_rtp_instance **instance);
/*! Callback for updating the destination that the remote side should send RTP to */
int (*update_peer)(struct ast_channel *chan, struct ast_rtp_instance *instance, struct ast_rtp_instance *vinstance, struct ast_rtp_instance *tinstance, const struct ast_format_cap *cap, int nat_active);
- /*! Callback for retrieving codecs that the channel can do. Result returned in result_cap. */
+ /*!
+ * \brief Callback for retrieving codecs that the channel can do. Result returned in result_cap.
+ * \note This function may be NULL for a given channel driver. This should be accounted for and if that is the case, this function is not used.
+ */
void (*get_codec)(struct ast_channel *chan, struct ast_format_cap *result_cap);
/*! Linked list information */
AST_RWLIST_ENTRY(ast_rtp_glue) entry;
@@ -1507,6 +1518,20 @@ void ast_rtp_codecs_payload_formats(struct ast_rtp_codecs *codecs, struct ast_fo
int ast_rtp_codecs_payload_code(struct ast_rtp_codecs *codecs, int asterisk_format, struct ast_format *format, int code);
/*!
+ * \brief Set a payload code for use with a specific Asterisk format
+ *
+ * \param codecs Codecs structure to manipulate
+ * \param code The payload code
+ * \param format Asterisk format
+ *
+ * \retval 0 Payload was set to the given format
+ * \retval -1 Payload was in use or could not be set
+ *
+ * \since 15.0.0
+ */
+int ast_rtp_codecs_payload_set_rx(struct ast_rtp_codecs *codecs, int code, struct ast_format *format);
+
+/*!
* \brief Retrieve a tx mapped payload type based on whether it is an Asterisk format and the code
* \since 14.0.0
*
@@ -2266,6 +2291,8 @@ int ast_rtp_instance_sendcng(struct ast_rtp_instance *instance, int level);
*
* \retval 0 Success
* \retval non-zero Failure
+ *
+ * \note If no remote policy is provided any existing SRTP policies are left and the new local policy is added
*/
int ast_rtp_instance_add_srtp_policy(struct ast_rtp_instance *instance, struct ast_srtp_policy* remote_policy, struct ast_srtp_policy *local_policy, int rtcp);
@@ -2411,6 +2438,36 @@ unsigned int ast_rtp_instance_get_ssrc(struct ast_rtp_instance *rtp);
*/
const char *ast_rtp_instance_get_cname(struct ast_rtp_instance *rtp);
+/*!
+ * \brief Request that an RTP instance be bundled with another
+ * \since 15.0.0
+ *
+ * \param child The child RTP instance
+ * \param parent The parent RTP instance the child should be bundled with
+ *
+ * \retval 0 success
+ * \retval -1 failure
+ */
+int ast_rtp_instance_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);
+
+/*!
+ * \brief Set the remote SSRC for an RTP instance
+ * \since 15.0.0
+ *
+ * \param rtp The RTP instance
+ * \param ssrc The remote SSRC
+ */
+void ast_rtp_instance_set_remote_ssrc(struct ast_rtp_instance *rtp, unsigned int ssrc);
+
+/*!
+ * \brief Set the stream number for an RTP instance
+ * \since 15.0.0
+ *
+ * \param rtp The RTP instance
+ * \param stream_num The stream identifier number
+ */
+void ast_rtp_instance_set_stream_num(struct ast_rtp_instance *instance, int stream_num);
+
/*! \addtogroup StasisTopicsAndMessages
* @{
*/
diff --git a/include/asterisk/sdp.h b/include/asterisk/sdp.h
index 224a0e5a3..768469544 100644
--- a/include/asterisk/sdp.h
+++ b/include/asterisk/sdp.h
@@ -254,16 +254,6 @@ void ast_sdp_s_free(struct ast_sdp_s_line *s_line);
void ast_sdp_t_free(struct ast_sdp_t_line *t_line);
/*!
- * \brief Free an SDP
- * Frees the sdp and all resources it contains
- *
- * \param sdp The sdp to free
- *
- * \since 15
- */
-void ast_sdp_free(struct ast_sdp *sdp);
-
-/*!
* \brief Allocate an SDP Attribute
*
* \param name Attribute Name
@@ -544,7 +534,7 @@ int ast_sdp_m_add_format(struct ast_sdp_m_line *m_line, const struct ast_sdp_opt
int rtp_code, int asterisk_format, const struct ast_format *format, int code);
/*!
- * \brief Create an SDP
+ * \brief Create an SDP ao2 object
*
* \param o_line Origin
* \param c_line Connection
diff --git a/include/asterisk/sdp_options.h b/include/asterisk/sdp_options.h
index f49b79483..e45ae8cb1 100644
--- a/include/asterisk/sdp_options.h
+++ b/include/asterisk/sdp_options.h
@@ -20,6 +20,7 @@
#define _ASTERISK_SDP_OPTIONS_H
#include "asterisk/udptl.h"
+#include "asterisk/format_cap.h"
struct ast_sdp_options;
@@ -80,6 +81,149 @@ enum ast_sdp_options_encryption {
};
/*!
+ * \brief Callback when processing an offer SDP for our answer SDP.
+ * \since 15.0.0
+ *
+ * \details
+ * This callback is called after merging our last negotiated topology
+ * with the remote's offer topology and before we have sent our answer
+ * SDP. At this point you can alter new_topology streams. You can
+ * decline, remove formats, or rename streams. Changing anything else
+ * on the streams is likely to not end well.
+ *
+ * * To decline a stream simply set the stream state to
+ * AST_STREAM_STATE_REMOVED. You could implement a maximum number
+ * of active streams of a given type policy.
+ *
+ * * To remove formats use the format API to remove any formats from a
+ * stream. The streams have the current joint negotiated formats.
+ * Most likely you would want to remove all but the first format.
+ *
+ * * To rename a stream you need to clone the stream and give it a
+ * new name and then set it in new_topology using
+ * ast_stream_topology_set_stream().
+ *
+ * \note Removing all formats is an error. You should decline the
+ * stream instead.
+ *
+ * \param context User supplied context data pointer for the SDP
+ * state.
+ * \param old_topology Active negotiated topology. NULL if this is
+ * the first SDP negotiation. The old topology is available so you
+ * can tell if any streams are new or changing type.
+ * \param new_topology New negotiated topology that we intend to
+ * generate the answer SDP.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_sdp_answerer_modify_cb)(void *context,
+ const struct ast_stream_topology *old_topology,
+ struct ast_stream_topology *new_topology);
+
+/*!
+ * \internal
+ * \brief Callback when generating a topology for our SDP offer.
+ * \since 15.0.0
+ *
+ * \details
+ * This callback is called after merging any topology updates from the
+ * system by ast_sdp_state_update_local_topology() and before we have
+ * sent our offer SDP. At this point you can alter new_topology
+ * streams. You can decline, add/remove/update formats, or rename
+ * streams. Changing anything else on the streams is likely to not
+ * end well.
+ *
+ * * To decline a stream simply set the stream state to
+ * AST_STREAM_STATE_REMOVED. You could implement a maximum number
+ * of active streams of a given type policy.
+ *
+ * * To update formats use the format API to change formats of the
+ * streams. The streams have the current proposed formats. You
+ * could do whatever you want for formats but you should stay within
+ * the configured formats for the stream type's endpoint. However,
+ * you should use ast_sdp_state_update_local_topology() instead of
+ * this backdoor method.
+ *
+ * * To rename a stream you need to clone the stream and give it a
+ * new name and then set it in new_topology using
+ * ast_stream_topology_set_stream().
+ *
+ * \note Removing all formats is an error. You should decline the
+ * stream instead.
+ *
+ * \note Declined new streams that are in slots higher than present in
+ * old_topology are removed so the SDP can be smaller. The remote has
+ * never seen those slots so we shouldn't bother keeping them.
+ *
+ * \param context User supplied context data pointer for the SDP
+ * state.
+ * \param old_topology Active negotiated topology. NULL if this is
+ * the first SDP negotiation. The old topology is available so you
+ * can tell if any streams are new or changing type.
+ * \param new_topology Merged topology that we intend to generate the
+ * offer SDP.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_sdp_offerer_modify_cb)(void *context,
+ const struct ast_stream_topology *old_topology,
+ struct ast_stream_topology *new_topology);
+
+/*!
+ * \brief Callback when generating an offer SDP to configure extra stream data.
+ * \since 15.0.0
+ *
+ * \details
+ * This callback is called after any ast_sdp_offerer_modify_cb
+ * callback and before we have sent our offer SDP. The callback can
+ * call several SDP API calls to configure the proposed capabilities
+ * of streams before we create the SDP offer. For example, the
+ * callback could configure a stream specific connection address, T.38
+ * parameters, RTP instance, or UDPTL instance parameters.
+ *
+ * \param context User supplied context data pointer for the SDP
+ * state.
+ * \param topology Topology ready to configure extra stream options.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_sdp_offerer_config_cb)(void *context, const struct ast_stream_topology *topology);
+
+/*!
+ * \brief Callback before applying a topology.
+ * \since 15.0.0
+ *
+ * \details
+ * This callback is called before the topology is applied so the
+ * using module can do what is necessary before the topology becomes
+ * active.
+ *
+ * \param context User supplied context data pointer for the SDP
+ * state.
+ * \param topology Topology ready to be applied.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_sdp_preapply_cb)(void *context, const struct ast_stream_topology *topology);
+
+/*!
+ * \brief Callback after applying a topology.
+ * \since 15.0.0
+ *
+ * \details
+ * This callback is called after the topology is applied so the
+ * using module can do what is necessary after the topology becomes
+ * active.
+ *
+ * \param context User supplied context data pointer for the SDP
+ * state.
+ * \param topology Topology already applied.
+ *
+ * \return Nothing
+ */
+typedef void (*ast_sdp_postapply_cb)(void *context, const struct ast_stream_topology *topology);
+
+/*!
* \since 15.0.0
* \brief Allocate a new SDP options structure.
*
@@ -204,6 +348,24 @@ void ast_sdp_options_set_rtp_engine(struct ast_sdp_options *options,
*/
const char *ast_sdp_options_get_rtp_engine(const struct ast_sdp_options *options);
+void ast_sdp_options_set_state_context(struct ast_sdp_options *options, void *state_context);
+void *ast_sdp_options_get_state_context(const struct ast_sdp_options *options);
+
+void ast_sdp_options_set_answerer_modify_cb(struct ast_sdp_options *options, ast_sdp_answerer_modify_cb answerer_modify_cb);
+ast_sdp_answerer_modify_cb ast_sdp_options_get_answerer_modify_cb(const struct ast_sdp_options *options);
+
+void ast_sdp_options_set_offerer_modify_cb(struct ast_sdp_options *options, ast_sdp_offerer_modify_cb offerer_modify_cb);
+ast_sdp_offerer_modify_cb ast_sdp_options_get_offerer_modify_cb(const struct ast_sdp_options *options);
+
+void ast_sdp_options_set_offerer_config_cb(struct ast_sdp_options *options, ast_sdp_offerer_config_cb offerer_config_cb);
+ast_sdp_offerer_config_cb ast_sdp_options_get_offerer_config_cb(const struct ast_sdp_options *options);
+
+void ast_sdp_options_set_preapply_cb(struct ast_sdp_options *options, ast_sdp_preapply_cb preapply_cb);
+ast_sdp_preapply_cb ast_sdp_options_get_preapply_cb(const struct ast_sdp_options *options);
+
+void ast_sdp_options_set_postapply_cb(struct ast_sdp_options *options, ast_sdp_postapply_cb postapply_cb);
+ast_sdp_postapply_cb ast_sdp_options_get_postapply_cb(const struct ast_sdp_options *options);
+
/*!
* \since 15.0.0
* \brief Set SDP Options rtp_symmetric
@@ -505,6 +667,26 @@ unsigned int ast_sdp_options_get_udptl_far_max_datagram(const struct ast_sdp_opt
/*!
* \since 15.0.0
+ * \brief Set SDP Options max_streams
+ *
+ * \param options SDP Options
+ * \param max_streams
+ */
+void ast_sdp_options_set_max_streams(struct ast_sdp_options *options,
+ unsigned int max_streams);
+
+/*!
+ * \since 15.0.0
+ * \brief Get SDP Options max_streams
+ *
+ * \param options SDP Options
+ *
+ * \returns max_streams
+ */
+unsigned int ast_sdp_options_get_max_streams(const struct ast_sdp_options *options);
+
+/*!
+ * \since 15.0.0
* \brief Enable setting SSRC level attributes on SDPs
*
* \param options SDP Options
@@ -522,4 +704,71 @@ void ast_sdp_options_set_ssrc(struct ast_sdp_options *options, unsigned int ssrc
*/
unsigned int ast_sdp_options_get_ssrc(const struct ast_sdp_options *options);
+/*!
+ * \brief Set the SDP options scheduler context used to create new streams of the type.
+ * \since 15.0.0
+ *
+ * \param options SDP Options
+ * \param type Media type the scheduler context is for.
+ * \param sched Scheduler context to use for the specified media type.
+ *
+ * \return Nothing
+ */
+void ast_sdp_options_set_sched_type(struct ast_sdp_options *options,
+ enum ast_media_type type, struct ast_sched_context *sched);
+
+/*!
+ * \brief Get the SDP options scheduler context used to create new streams of the type.
+ * \since 15.0.0
+ *
+ * \param options SDP Options
+ * \param type Media type the format cap represents.
+ *
+ * \return The stored scheduler context to create new streams of the type.
+ */
+struct ast_sched_context *ast_sdp_options_get_sched_type(const struct ast_sdp_options *options,
+ enum ast_media_type type);
+
+/*!
+ * \brief Set all allowed stream types to create new streams.
+ * \since 15.0.0
+ *
+ * \param options SDP Options
+ * \param cap Format capabilities to set all allowed stream types at once.
+ * Could be NULL to disable creating any new streams.
+ *
+ * \return Nothing
+ */
+void ast_sdp_options_set_format_caps(struct ast_sdp_options *options,
+ struct ast_format_cap *cap);
+
+/*!
+ * \brief Set the SDP options format cap used to create new streams of the type.
+ * \since 15.0.0
+ *
+ * \param options SDP Options
+ * \param type Media type the format cap represents.
+ * \param cap Format capabilities to use for the specified media type.
+ * Could be NULL to disable creating new streams of type.
+ *
+ * \return Nothing
+ */
+void ast_sdp_options_set_format_cap_type(struct ast_sdp_options *options,
+ enum ast_media_type type, struct ast_format_cap *cap);
+
+/*!
+ * \brief Get the SDP options format cap used to create new streams of the type.
+ * \since 15.0.0
+ *
+ * \param options SDP Options
+ * \param type Media type the format cap represents.
+ *
+ * \retval NULL if stream not allowed to be created.
+ * \retval cap to use in negotiating the new stream.
+ *
+ * \note The returned cap does not have its own ao2 ref.
+ */
+struct ast_format_cap *ast_sdp_options_get_format_cap_type(const struct ast_sdp_options *options,
+ enum ast_media_type type);
+
#endif /* _ASTERISK_SDP_OPTIONS_H */
diff --git a/include/asterisk/sdp_state.h b/include/asterisk/sdp_state.h
index b8209e1d5..ec9d502e2 100644
--- a/include/asterisk/sdp_state.h
+++ b/include/asterisk/sdp_state.h
@@ -30,12 +30,22 @@ struct ast_control_t38_parameters;
/*!
* \brief Allocate a new SDP state
*
+ * \details
* SDP state keeps tabs on everything SDP-related for a media session.
* Most SDP operations will require the state to be provided.
* Ownership of the SDP options is taken on by the SDP state.
* A good strategy is to call this during session creation.
+ *
+ * \param topology Initial stream topology to offer.
+ * NULL if we are going to be the answerer. We can always
+ * update the local topology later if it turns out we need
+ * to be the offerer.
+ * \param options SDP options for the duration of the session.
+ *
+ * \retval SDP state struct
+ * \retval NULL on failure
*/
-struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams,
+struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *topology,
struct ast_sdp_options *options);
/*!
@@ -86,6 +96,8 @@ int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_
*
* If this is called prior to receiving a remote SDP, then this will just mirror
* the local configured endpoint capabilities.
+ *
+ * \note Cannot return NULL. It is a BUG if it does.
*/
const struct ast_stream_topology *ast_sdp_state_get_joint_topology(
const struct ast_sdp_state *sdp_state);
@@ -93,6 +105,7 @@ const struct ast_stream_topology *ast_sdp_state_get_joint_topology(
/*!
* \brief Get the local topology
*
+ * \note Cannot return NULL. It is a BUG if it does.
*/
const struct ast_stream_topology *ast_sdp_state_get_local_topology(
const struct ast_sdp_state *sdp_state);
@@ -114,9 +127,10 @@ const struct ast_sdp_options *ast_sdp_state_get_options(
* \retval NULL Failure
*
* \note
- * This function will allocate a new SDP with RTP instances if it has not already
- * been allocated.
- *
+ * This function will return the last local SDP created if one were
+ * previously requested for the current negotiation. Otherwise it
+ * creates our SDP offer/answer depending on what role we are playing
+ * in the current negotiation.
*/
const struct ast_sdp *ast_sdp_state_get_local_sdp(struct ast_sdp_state *sdp_state);
@@ -152,6 +166,7 @@ const void *ast_sdp_state_get_local_sdp_impl(struct ast_sdp_state *sdp_state);
*
* \retval 0 Success
* \retval non-0 Failure
+ * Use ast_sdp_state_is_offer_rejected() to see if the SDP offer was rejected.
*
* \since 15
*/
@@ -165,39 +180,72 @@ int ast_sdp_state_set_remote_sdp(struct ast_sdp_state *sdp_state, const struct a
*
* \retval 0 Success
* \retval non-0 Failure
+ * Use ast_sdp_state_is_offer_rejected() to see if the SDP offer was rejected.
*
* \since 15
*/
int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, const void *remote);
/*!
- * \brief Reset the SDP state and stream capabilities as if the SDP state had just been allocated.
+ * \brief Was the set remote offer rejected.
+ * \since 15.0.0
*
* \param sdp_state
- * \param remote The implementation's representation of an SDP.
*
- * \retval 0 Success
+ * \retval 0 if not rejected.
+ * \retval non-zero if rejected.
+ */
+int ast_sdp_state_is_offer_rejected(struct ast_sdp_state *sdp_state);
+
+/*!
+ * \brief Are we the SDP offerer.
+ * \since 15.0.0
*
- * \note
- * This is most useful for when a channel driver is sending a session refresh message
- * and needs to re-advertise its initial capabilities instead of the previously-negotiated
- * joint capabilities.
+ * \param sdp_state
*
- * \since 15
+ * \retval 0 if we are not the offerer.
+ * \retval non-zero we are the offerer.
*/
-int ast_sdp_state_reset(struct ast_sdp_state *sdp_state);
+int ast_sdp_state_is_offerer(struct ast_sdp_state *sdp_state);
+
+/*!
+ * \brief Are we the SDP answerer.
+ * \since 15.0.0
+ *
+ * \param sdp_state
+ *
+ * \retval 0 if we are not the answerer.
+ * \retval non-zero we are the answerer.
+ */
+int ast_sdp_state_is_answerer(struct ast_sdp_state *sdp_state);
+
+/*!
+ * \brief Restart the SDP offer/answer negotiations.
+ *
+ * \param sdp_state
+ *
+ * \retval 0 Success
+ * \retval non-0 Failure
+ */
+int ast_sdp_state_restart_negotiations(struct ast_sdp_state *sdp_state);
/*!
* \brief Update the local stream topology on the SDP state.
*
+ * \details
+ * Basically we are saving off any topology updates until we create the
+ * next SDP offer. Repeated updates merge with the previous updated
+ * topology.
+ *
* \param sdp_state
- * \param streams The new stream topology.
+ * \param topology The new stream topology.
*
* \retval 0 Success
+ * \retval non-0 Failure
*
* \since 15
*/
-int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *streams);
+int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *topology);
/*!
* \brief Set the local address (IP address) to use for connection addresses
@@ -231,7 +279,26 @@ int ast_sdp_state_set_connection_address(struct ast_sdp_state *sdp_state, int st
/*!
* \since 15.0.0
- * \brief Set a stream to be held or unheld
+ * \brief Set the global locally held state.
+ *
+ * \param sdp_state
+ * \param locally_held
+ */
+void ast_sdp_state_set_global_locally_held(struct ast_sdp_state *sdp_state, unsigned int locally_held);
+
+/*!
+ * \since 15.0.0
+ * \brief Get the global locally held state.
+ *
+ * \param sdp_state
+ *
+ * \returns locally_held
+ */
+unsigned int ast_sdp_state_get_global_locally_held(const struct ast_sdp_state *sdp_state);
+
+/*!
+ * \since 15.0.0
+ * \brief Set a stream to be held or unheld locally
*
* \param sdp_state
* \param stream_index The stream to set the held value for
@@ -242,25 +309,37 @@ void ast_sdp_state_set_locally_held(struct ast_sdp_state *sdp_state,
/*!
* \since 15.0.0
- * \brief Set the UDPTL session parameters
+ * \brief Get whether a stream is locally held or not
*
* \param sdp_state
- * \param stream_index The stream to set the UDPTL session parameters for
- * \param params
+ * \param stream_index The stream to get the held state for
+ *
+ * \returns locally_held
*/
-void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state,
- int stream_index, struct ast_control_t38_parameters *params);
+unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state,
+ int stream_index);
/*!
* \since 15.0.0
- * \brief Get whether a stream is held or not
+ * \brief Get whether a stream is remotely held or not
*
* \param sdp_state
* \param stream_index The stream to get the held state for
*
- * \returns locally_held
+ * \returns remotely_held
*/
-unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state,
+unsigned int ast_sdp_state_get_remotely_held(const struct ast_sdp_state *sdp_state,
int stream_index);
+/*!
+ * \since 15.0.0
+ * \brief Set the UDPTL session parameters
+ *
+ * \param sdp_state
+ * \param stream_index The stream to set the UDPTL session parameters for
+ * \param params
+ */
+void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state,
+ int stream_index, struct ast_control_t38_parameters *params);
+
#endif /* _ASTERISK_SDP_STATE_H */
diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h
index 00169a3f1..2c1053a7b 100644
--- a/include/asterisk/stream.h
+++ b/include/asterisk/stream.h
@@ -228,6 +228,17 @@ void ast_stream_set_state(struct ast_stream *stream, enum ast_stream_state state
const char *ast_stream_state2str(enum ast_stream_state state);
/*!
+ * \brief Convert a string to a stream state
+ *
+ * \param str The string to convert
+ *
+ * \return The stream state
+ *
+ * \since 15.0.0
+ */
+enum ast_stream_state ast_stream_str2state(const char *str);
+
+/*!
* \brief Get the opaque stream data
*
* \param stream The media stream
@@ -290,6 +301,20 @@ struct ast_stream_topology *ast_stream_topology_clone(
const struct ast_stream_topology *topology);
/*!
+ * \brief Compare two stream topologies to see if they are equal
+ *
+ * \param left The left topology
+ * \param right The right topology
+ *
+ * \retval 1 topologies are equivalent
+ * \retval 0 topologies differ
+ *
+ * \since 15
+ */
+int ast_stream_topology_equal(const struct ast_stream_topology *left,
+ const struct ast_stream_topology *right);
+
+/*!
* \brief Destroy a stream topology
*
* \param topology The topology of streams
@@ -391,7 +416,7 @@ int ast_stream_topology_del_stream(struct ast_stream_topology *topology,
* since a new format capabilities structure is created for each media type.
*
* \note Each stream will have its name set to the corresponding media type.
- * For example: "AST_MEDIA_TYPE_AUDIO".
+ * For example: "audio".
*
* \note Each stream will be set to the sendrecv state.
*
diff --git a/main/app.c b/main/app.c
index 1eb0741ee..69c96c06c 100644
--- a/main/app.c
+++ b/main/app.c
@@ -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;
}
}
diff --git a/main/asterisk.c b/main/asterisk.c
index 4424022c9..a302836a4 100644
--- a/main/asterisk.c
+++ b/main/asterisk.c
@@ -3699,7 +3699,9 @@ static void ast_readconfig(void)
/* Set the maximum amount of open files */
} else if (!strcasecmp(v->name, "maxfiles")) {
ast_option_maxfiles = atoi(v->value);
- set_ulimit(ast_option_maxfiles);
+ if (!ast_opt_remote) {
+ set_ulimit(ast_option_maxfiles);
+ }
/* What user to run as */
} else if (!strcasecmp(v->name, "runuser")) {
ast_copy_string(cfg_paths.run_user, v->value, sizeof(cfg_paths.run_user));
@@ -4583,7 +4585,6 @@ static void asterisk_daemon(int isroot, const char *runuser, const char *rungrou
check_init(app_init(), "App Core");
check_init(devstate_init(), "Device State Core");
check_init(ast_msg_init(), "Messaging API");
- check_init(ast_data_init(), "Data Retrieval API");
check_init(ast_channels_init(), "Channel");
check_init(ast_endpoint_init(), "Endpoints");
check_init(ast_pickup_init(), "Call Pickup");
diff --git a/main/bridge.c b/main/bridge.c
index 8cde62cb5..a1a1a6f55 100644
--- a/main/bridge.c
+++ b/main/bridge.c
@@ -3816,6 +3816,13 @@ void ast_bridge_set_sfu_video_mode(struct ast_bridge *bridge)
ast_bridge_unlock(bridge);
}
+void ast_bridge_set_video_update_discard(struct ast_bridge *bridge, unsigned int video_update_discard)
+{
+ ast_bridge_lock(bridge);
+ bridge->softmix.video_mode.video_update_discard = video_update_discard;
+ ast_bridge_unlock(bridge);
+}
+
void ast_bridge_update_talker_src_video_mode(struct ast_bridge *bridge, struct ast_channel *chan, int talker_energy, int is_keyframe)
{
struct ast_bridge_video_talker_src_data *data;
@@ -4276,14 +4283,15 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
BRIDGE_LOCK_ONE_OR_BOTH(bridge1, bridge2);
if (bridge2) {
+ void *tech;
struct ast_channel *locals[2];
/* Have to lock everything just in case a hangup comes in early */
- ast_local_lock_all(local_chan, &locals[0], &locals[1]);
+ ast_local_lock_all(local_chan, &tech, &locals[0], &locals[1]);
if (!locals[0] || !locals[1]) {
ast_log(LOG_ERROR, "Transfer failed probably due to an early hangup - "
"missing other half of '%s'\n", ast_channel_name(local_chan));
- ast_local_unlock_all(local_chan);
+ ast_local_unlock_all(tech, locals[0], locals[1]);
ao2_cleanup(local_chan);
return AST_BRIDGE_TRANSFER_FAIL;
}
@@ -4294,7 +4302,7 @@ static enum ast_transfer_result attended_transfer_bridge(struct ast_channel *cha
}
ast_attended_transfer_message_add_link(transfer_msg, locals);
- ast_local_unlock_all(local_chan);
+ ast_local_unlock_all(tech, locals[0], locals[1]);
} else {
ast_attended_transfer_message_add_app(transfer_msg, app, local_chan);
}
@@ -4822,7 +4830,7 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
res = AST_BRIDGE_TRANSFER_SUCCESS;
end:
- if (res == AST_BRIDGE_TRANSFER_SUCCESS && hangup_target) {
+ if ((res == AST_BRIDGE_TRANSFER_SUCCESS && hangup_target) || res == AST_BRIDGE_TRANSFER_FAIL) {
ast_softhangup(to_transfer_target, AST_SOFTHANGUP_DEV);
}
diff --git a/main/bridge_channel.c b/main/bridge_channel.c
index e8ab8a898..2e943000c 100644
--- a/main/bridge_channel.c
+++ b/main/bridge_channel.c
@@ -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) {
diff --git a/main/cdr.c b/main/cdr.c
index 214af2cbc..1817e80a7 100644
--- a/main/cdr.c
+++ b/main/cdr.c
@@ -60,7 +60,6 @@
#include "asterisk/config.h"
#include "asterisk/cli.h"
#include "asterisk/stringfields.h"
-#include "asterisk/data.h"
#include "asterisk/config_options.h"
#include "asterisk/json.h"
#include "asterisk/parking.h"
diff --git a/main/channel.c b/main/channel.c
index 1ca485e7a..66825559c 100644
--- a/main/channel.c
+++ b/main/channel.c
@@ -66,7 +66,6 @@
#include "asterisk/autochan.h"
#include "asterisk/stringfields.h"
#include "asterisk/global_datastores.h"
-#include "asterisk/data.h"
#include "asterisk/channel_internal.h"
#include "asterisk/features.h"
#include "asterisk/bridge.h"
@@ -998,6 +997,9 @@ __ast_channel_alloc_ap(int needqueue, int state, const char *cid_num, const char
* the world know of its existance
*/
ast_channel_stage_snapshot_done(tmp);
+
+ ast_debug(1, "Channel %p '%s' allocated\n", tmp, ast_channel_name(tmp));
+
return tmp;
}
@@ -2217,6 +2219,8 @@ static void ast_channel_destructor(void *obj)
char device_name[AST_CHANNEL_NAME];
ast_callid callid;
+ ast_debug(1, "Channel %p '%s' destroying\n", chan, ast_channel_name(chan));
+
/* Stop monitoring */
if (ast_channel_monitor(chan)) {
ast_channel_monitor(chan)->stop(chan, 0);
@@ -2579,6 +2583,9 @@ void ast_hangup(struct ast_channel *chan)
return;
}
+ ast_debug(1, "Channel %p '%s' hanging up. Refs: %d\n", chan, ast_channel_name(chan),
+ ao2_ref(chan, 0));
+
ast_autoservice_stop(chan);
ast_channel_lock(chan);
@@ -2638,7 +2645,6 @@ void ast_hangup(struct ast_channel *chan)
ast_assert(ast_test_flag(ast_channel_flags(chan), AST_FLAG_BLOCKING) == 0);
}
- ast_debug(1, "Hanging up channel '%s'\n", ast_channel_name(chan));
if (ast_channel_tech(chan)->hangup) {
ast_channel_tech(chan)->hangup(chan);
}
@@ -3154,7 +3160,7 @@ int ast_waitfor(struct ast_channel *c, int ms)
int ast_waitfordigit(struct ast_channel *c, int ms)
{
- return ast_waitfordigit_full(c, ms, -1, -1);
+ return ast_waitfordigit_full(c, ms, NULL, -1, -1);
}
int ast_settimeout(struct ast_channel *c, unsigned int rate, int (*func)(const void *data), void *data)
@@ -3216,7 +3222,7 @@ int ast_settimeout_full(struct ast_channel *c, unsigned int rate, int (*func)(co
return res;
}
-int ast_waitfordigit_full(struct ast_channel *c, int timeout_ms, int audiofd, int cmdfd)
+int ast_waitfordigit_full(struct ast_channel *c, int timeout_ms, const char *breakon, int audiofd, int cmdfd)
{
struct timeval start = ast_tvnow();
int ms;
@@ -3255,17 +3261,24 @@ int ast_waitfordigit_full(struct ast_channel *c, int timeout_ms, int audiofd, in
} else if (rchan) {
int res;
struct ast_frame *f = ast_read(c);
- if (!f)
+
+ if (!f) {
+ ast_channel_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
+
return -1;
+ }
switch (f->frametype) {
case AST_FRAME_DTMF_BEGIN:
break;
case AST_FRAME_DTMF_END:
res = f->subclass.integer;
- ast_frfree(f);
- ast_channel_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
- return res;
+ if (!breakon || strchr(breakon, res)) {
+ ast_frfree(f);
+ ast_channel_clear_flag(c, AST_FLAG_END_DTMF_ONLY);
+ return res;
+ }
+ break;
case AST_FRAME_CONTROL:
switch (f->subclass.integer) {
case AST_CONTROL_HANGUP:
@@ -4170,6 +4183,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);
@@ -4921,17 +4939,28 @@ int ast_write_stream(struct ast_channel *chan, int stream_num, struct ast_frame
goto done;
}
- /* If this frame is writing an audio or video frame get the stream information */
- if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO) {
- /* Initially use the default stream unless an explicit stream is provided */
- stream = default_stream = ast_channel_get_default_stream(chan, ast_format_get_type(fr->subclass.format));
+ if (stream_num >= 0) {
+ /* If we were told to write to an explicit stream then allow this frame through, no matter
+ * if the type is expected or not (a framehook could change)
+ */
+ if (stream_num >= ast_stream_topology_get_count(ast_channel_get_stream_topology(chan))) {
+ goto done;
+ }
+ stream = ast_stream_topology_get_stream(ast_channel_get_stream_topology(chan), stream_num);
+ default_stream = ast_channel_get_default_stream(chan, ast_stream_get_type(stream));
+ } else if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO || fr->frametype == AST_FRAME_MODEM) {
+ /* If we haven't been told of a stream then we need to figure out which once we need */
+ enum ast_media_type type = AST_MEDIA_TYPE_UNKNOWN;
- if (stream_num >= 0) {
- if (stream_num >= ast_stream_topology_get_count(ast_channel_get_stream_topology(chan))) {
- goto done;
- }
- stream = ast_stream_topology_get_stream(ast_channel_get_stream_topology(chan), stream_num);
+ /* Some frame types have a fixed media type */
+ if (fr->frametype == AST_FRAME_VOICE || fr->frametype == AST_FRAME_VIDEO) {
+ type = ast_format_get_type(fr->subclass.format);
+ } else if (fr->frametype == AST_FRAME_MODEM) {
+ type = AST_MEDIA_TYPE_IMAGE;
}
+
+ /* No stream was specified, so use the default one */
+ stream = default_stream = ast_channel_get_default_stream(chan, type);
}
/* Perform the framehook write event here. After the frame enters the framehook list
@@ -5028,12 +5057,16 @@ int ast_write_stream(struct ast_channel *chan, int stream_num, struct ast_frame
res = ast_channel_tech(chan)->write_video(chan, fr);
} else {
res = 0;
-
}
break;
case AST_FRAME_MODEM:
- res = (ast_channel_tech(chan)->write == NULL) ? 0 :
- ast_channel_tech(chan)->write(chan, fr);
+ if (ast_channel_tech(chan)->write_stream) {
+ res = ast_channel_tech(chan)->write_stream(chan, ast_stream_get_position(stream), fr);
+ } else if ((stream == default_stream) && ast_channel_tech(chan)->write) {
+ res = ast_channel_tech(chan)->write(chan, fr);
+ } else {
+ res = 0;
+ }
break;
case AST_FRAME_VOICE:
if (ast_opt_generic_plc && ast_format_cmp(fr->subclass.format, ast_format_slin) == AST_FORMAT_CMP_EQUAL) {
@@ -6326,11 +6359,11 @@ int ast_readstring_full(struct ast_channel *c, char *s, int len, int timeout, in
silgen = ast_channel_start_silence_generator(c);
usleep(1000);
if (!d)
- d = ast_waitfordigit_full(c, to, audiofd, ctrlfd);
+ d = ast_waitfordigit_full(c, to, NULL, audiofd, ctrlfd);
} else {
if (!silgen && ast_opt_transmit_silence)
silgen = ast_channel_start_silence_generator(c);
- d = ast_waitfordigit_full(c, to, audiofd, ctrlfd);
+ d = ast_waitfordigit_full(c, to, NULL, audiofd, ctrlfd);
}
if (d < 0) {
ast_channel_stop_silence_generator(c, silgen);
@@ -7544,122 +7577,6 @@ int ast_plc_reload(void)
/*!
* \internal
- * \brief Implements the channels provider.
- */
-static int data_channels_provider_handler(const struct ast_data_search *search,
- struct ast_data *root)
-{
- struct ast_channel *c;
- struct ast_channel_iterator *iter = NULL;
- struct ast_data *data_channel;
-
- for (iter = ast_channel_iterator_all_new();
- iter && (c = ast_channel_iterator_next(iter)); ast_channel_unref(c)) {
- ast_channel_lock(c);
-
- data_channel = ast_data_add_node(root, "channel");
- if (!data_channel) {
- ast_channel_unlock(c);
- continue;
- }
-
- if (ast_channel_data_add_structure(data_channel, c, 1) < 0) {
- ast_log(LOG_ERROR, "Unable to add channel structure for channel: %s\n", ast_channel_name(c));
- }
-
- ast_channel_unlock(c);
-
- if (!ast_data_search_match(search, data_channel)) {
- ast_data_remove_node(root, data_channel);
- }
- }
- if (iter) {
- ast_channel_iterator_destroy(iter);
- }
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief Implements the channeltypes provider.
- */
-static int data_channeltypes_provider_handler(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- struct chanlist *cl;
- struct ast_data *data_type;
-
- AST_RWLIST_RDLOCK(&backends);
- AST_RWLIST_TRAVERSE(&backends, cl, list) {
- data_type = ast_data_add_node(data_root, "type");
- if (!data_type) {
- continue;
- }
- ast_data_add_str(data_type, "name", cl->tech->type);
- ast_data_add_str(data_type, "description", cl->tech->description);
- ast_data_add_bool(data_type, "devicestate", cl->tech->devicestate ? 1 : 0);
- ast_data_add_bool(data_type, "presencestate", cl->tech->presencestate ? 1 : 0);
- ast_data_add_bool(data_type, "indications", cl->tech->indicate ? 1 : 0);
- ast_data_add_bool(data_type, "transfer", cl->tech->transfer ? 1 : 0);
- ast_data_add_bool(data_type, "send_digit_begin", cl->tech->send_digit_begin ? 1 : 0);
- ast_data_add_bool(data_type, "send_digit_end", cl->tech->send_digit_end ? 1 : 0);
- ast_data_add_bool(data_type, "call", cl->tech->call ? 1 : 0);
- ast_data_add_bool(data_type, "hangup", cl->tech->hangup ? 1 : 0);
- ast_data_add_bool(data_type, "answer", cl->tech->answer ? 1 : 0);
- ast_data_add_bool(data_type, "read", cl->tech->read ? 1 : 0);
- ast_data_add_bool(data_type, "write", cl->tech->write ? 1 : 0);
- ast_data_add_bool(data_type, "send_text", cl->tech->send_text ? 1 : 0);
- ast_data_add_bool(data_type, "send_image", cl->tech->send_image ? 1 : 0);
- ast_data_add_bool(data_type, "send_html", cl->tech->send_html ? 1 : 0);
- ast_data_add_bool(data_type, "exception", cl->tech->exception ? 1 : 0);
- ast_data_add_bool(data_type, "early_bridge", cl->tech->early_bridge ? 1 : 0);
- ast_data_add_bool(data_type, "fixup", cl->tech->fixup ? 1 : 0);
- ast_data_add_bool(data_type, "setoption", cl->tech->setoption ? 1 : 0);
- ast_data_add_bool(data_type, "queryoption", cl->tech->queryoption ? 1 : 0);
- ast_data_add_bool(data_type, "write_video", cl->tech->write_video ? 1 : 0);
- ast_data_add_bool(data_type, "write_text", cl->tech->write_text ? 1 : 0);
- ast_data_add_bool(data_type, "func_channel_read", cl->tech->func_channel_read ? 1 : 0);
- ast_data_add_bool(data_type, "func_channel_write", cl->tech->func_channel_write ? 1 : 0);
- ast_data_add_bool(data_type, "get_pvt_uniqueid", cl->tech->get_pvt_uniqueid ? 1 : 0);
- ast_data_add_bool(data_type, "cc_callback", cl->tech->cc_callback ? 1 : 0);
-
- ast_data_add_codecs(data_type, "capabilities", cl->tech->capabilities);
-
- if (!ast_data_search_match(search, data_type)) {
- ast_data_remove_node(data_root, data_type);
- }
- }
- AST_RWLIST_UNLOCK(&backends);
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief /asterisk/core/channels provider.
- */
-static const struct ast_data_handler channels_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = data_channels_provider_handler
-};
-
-/*!
- * \internal
- * \brief /asterisk/core/channeltypes provider.
- */
-static const struct ast_data_handler channeltypes_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = data_channeltypes_provider_handler
-};
-
-static const struct ast_data_entry channel_providers[] = {
- AST_DATA_ENTRY("/asterisk/core/channels", &channels_provider),
- AST_DATA_ENTRY("/asterisk/core/channeltypes", &channeltypes_provider),
-};
-
-/*!
- * \internal
* \brief Print channel object key (name).
* \since 12.0.0
*
@@ -7857,7 +7774,6 @@ static void channels_shutdown(void)
free_external_channelvars(&ami_vars);
free_external_channelvars(&ari_vars);
- ast_data_unregister(NULL);
ast_cli_unregister_multiple(cli_channel, ARRAY_LEN(cli_channel));
if (channels) {
ao2_container_unregister("channels");
@@ -7882,8 +7798,6 @@ int ast_channels_init(void)
ast_cli_register_multiple(cli_channel, ARRAY_LEN(cli_channel));
- ast_data_register_multiple_core(channel_providers, ARRAY_LEN(channel_providers));
-
ast_plc_reload();
ast_register_cleanup(channels_shutdown);
@@ -10941,6 +10855,12 @@ int ast_channel_request_stream_topology_change(struct ast_channel *chan,
return -1;
}
+ if (ast_stream_topology_equal(ast_channel_get_stream_topology(chan), topology)) {
+ ast_debug(3, "Topology of %s already matches what is requested so ignoring topology change request\n",
+ ast_channel_name(chan));
+ return 0;
+ }
+
ast_channel_internal_set_stream_topology_change_source(chan, change_source);
return ast_channel_tech(chan)->indicate(chan, AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE, topology, sizeof(topology));
diff --git a/main/channel_internal_api.c b/main/channel_internal_api.c
index 5e7df8983..d31ce94d8 100644
--- a/main/channel_internal_api.c
+++ b/main/channel_internal_api.c
@@ -40,7 +40,6 @@
#include "asterisk/paths.h"
#include "asterisk/channel.h"
#include "asterisk/channel_internal.h"
-#include "asterisk/data.h"
#include "asterisk/endpoints.h"
#include "asterisk/indications.h"
#include "asterisk/stasis_cache_pattern.h"
@@ -226,211 +225,6 @@ struct ast_channel {
/*! \brief The monotonically increasing integer counter for channel uniqueids */
static int uniqueint;
-/* AST_DATA definitions, which will probably have to be re-thought since the channel will be opaque */
-
-#if 0 /* XXX AstData: ast_callerid no longer exists. (Equivalent code not readily apparent.) */
-#define DATA_EXPORT_CALLERID(MEMBER) \
- MEMBER(ast_callerid, cid_dnid, AST_DATA_STRING) \
- MEMBER(ast_callerid, cid_num, AST_DATA_STRING) \
- MEMBER(ast_callerid, cid_name, AST_DATA_STRING) \
- MEMBER(ast_callerid, cid_ani, AST_DATA_STRING) \
- MEMBER(ast_callerid, cid_pres, AST_DATA_INTEGER) \
- MEMBER(ast_callerid, cid_ani2, AST_DATA_INTEGER) \
- MEMBER(ast_callerid, cid_tag, AST_DATA_STRING)
-
-AST_DATA_STRUCTURE(ast_callerid, DATA_EXPORT_CALLERID);
-#endif
-
-#define DATA_EXPORT_CHANNEL(MEMBER) \
- MEMBER(ast_channel, blockproc, AST_DATA_STRING) \
- MEMBER(ast_channel, appl, AST_DATA_STRING) \
- MEMBER(ast_channel, data, AST_DATA_STRING) \
- MEMBER(ast_channel, name, AST_DATA_STRING) \
- MEMBER(ast_channel, language, AST_DATA_STRING) \
- MEMBER(ast_channel, musicclass, AST_DATA_STRING) \
- MEMBER(ast_channel, accountcode, AST_DATA_STRING) \
- MEMBER(ast_channel, peeraccount, AST_DATA_STRING) \
- MEMBER(ast_channel, userfield, AST_DATA_STRING) \
- MEMBER(ast_channel, call_forward, AST_DATA_STRING) \
- MEMBER(ast_channel, parkinglot, AST_DATA_STRING) \
- MEMBER(ast_channel, hangupsource, AST_DATA_STRING) \
- MEMBER(ast_channel, dialcontext, AST_DATA_STRING) \
- MEMBER(ast_channel, rings, AST_DATA_INTEGER) \
- MEMBER(ast_channel, priority, AST_DATA_INTEGER) \
- MEMBER(ast_channel, macropriority, AST_DATA_INTEGER) \
- MEMBER(ast_channel, adsicpe, AST_DATA_INTEGER) \
- MEMBER(ast_channel, fin, AST_DATA_UNSIGNED_INTEGER) \
- MEMBER(ast_channel, fout, AST_DATA_UNSIGNED_INTEGER) \
- MEMBER(ast_channel, emulate_dtmf_duration, AST_DATA_UNSIGNED_INTEGER) \
- MEMBER(ast_channel, visible_indication, AST_DATA_INTEGER) \
- MEMBER(ast_channel, context, AST_DATA_STRING) \
- MEMBER(ast_channel, exten, AST_DATA_STRING) \
- MEMBER(ast_channel, macrocontext, AST_DATA_STRING) \
- MEMBER(ast_channel, macroexten, AST_DATA_STRING)
-
-AST_DATA_STRUCTURE(ast_channel, DATA_EXPORT_CHANNEL);
-
-static void channel_data_add_flags(struct ast_data *tree,
- struct ast_channel *chan)
-{
- ast_data_add_bool(tree, "DEFER_DTMF", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DEFER_DTMF));
- ast_data_add_bool(tree, "WRITE_INT", ast_test_flag(ast_channel_flags(chan), AST_FLAG_WRITE_INT));
- ast_data_add_bool(tree, "BLOCKING", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BLOCKING));
- ast_data_add_bool(tree, "ZOMBIE", ast_test_flag(ast_channel_flags(chan), AST_FLAG_ZOMBIE));
- ast_data_add_bool(tree, "EXCEPTION", ast_test_flag(ast_channel_flags(chan), AST_FLAG_EXCEPTION));
- ast_data_add_bool(tree, "MOH", ast_test_flag(ast_channel_flags(chan), AST_FLAG_MOH));
- ast_data_add_bool(tree, "SPYING", ast_test_flag(ast_channel_flags(chan), AST_FLAG_SPYING));
- ast_data_add_bool(tree, "IN_AUTOLOOP", ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_AUTOLOOP));
- ast_data_add_bool(tree, "OUTGOING", ast_test_flag(ast_channel_flags(chan), AST_FLAG_OUTGOING));
- ast_data_add_bool(tree, "IN_DTMF", ast_test_flag(ast_channel_flags(chan), AST_FLAG_IN_DTMF));
- ast_data_add_bool(tree, "EMULATE_DTMF", ast_test_flag(ast_channel_flags(chan), AST_FLAG_EMULATE_DTMF));
- ast_data_add_bool(tree, "END_DTMF_ONLY", ast_test_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY));
- ast_data_add_bool(tree, "MASQ_NOSTREAM", ast_test_flag(ast_channel_flags(chan), AST_FLAG_MASQ_NOSTREAM));
- ast_data_add_bool(tree, "BRIDGE_HANGUP_RUN", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_HANGUP_RUN));
- ast_data_add_bool(tree, "DISABLE_WORKAROUNDS", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_WORKAROUNDS));
- ast_data_add_bool(tree, "DISABLE_DEVSTATE_CACHE", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DISABLE_DEVSTATE_CACHE));
- ast_data_add_bool(tree, "BRIDGE_DUAL_REDIRECT_WAIT", ast_test_flag(ast_channel_flags(chan), AST_FLAG_BRIDGE_DUAL_REDIRECT_WAIT));
- ast_data_add_bool(tree, "ORIGINATED", ast_test_flag(ast_channel_flags(chan), AST_FLAG_ORIGINATED));
- ast_data_add_bool(tree, "DEAD", ast_test_flag(ast_channel_flags(chan), AST_FLAG_DEAD));
-}
-
-int ast_channel_data_add_structure(struct ast_data *tree,
- struct ast_channel *chan, int add_bridged)
-{
- struct ast_data *data_bridged;
- struct ast_data *data_cdr;
- struct ast_data *data_flags;
- struct ast_data *data_zones;
- struct ast_data *enum_node;
- struct ast_data *data_softhangup;
-#if 0 /* XXX AstData: ast_callerid no longer exists. (Equivalent code not readily apparent.) */
- struct ast_data *data_callerid;
- char value_str[100];
-#endif
-
- if (!tree) {
- return -1;
- }
-
- ast_data_add_structure(ast_channel, tree, chan);
-
- if (add_bridged) {
- RAII_VAR(struct ast_channel *, bc, ast_channel_bridge_peer(chan), ast_channel_cleanup);
- if (bc) {
- data_bridged = ast_data_add_node(tree, "bridged");
- if (!data_bridged) {
- return -1;
- }
- ast_channel_data_add_structure(data_bridged, bc, 0);
- }
- }
-
- ast_data_add_str(tree, "uniqueid", ast_channel_uniqueid(chan));
- ast_data_add_str(tree, "linkedid", ast_channel_linkedid(chan));
-
- ast_data_add_codec(tree, "oldwriteformat", ast_channel_oldwriteformat(chan));
- ast_data_add_codec(tree, "readformat", ast_channel_readformat(chan));
- ast_data_add_codec(tree, "writeformat", ast_channel_writeformat(chan));
- ast_data_add_codec(tree, "rawreadformat", ast_channel_rawreadformat(chan));
- ast_data_add_codec(tree, "rawwriteformat", ast_channel_rawwriteformat(chan));
- ast_data_add_codecs(tree, "nativeformats", ast_channel_nativeformats(chan));
-
- /* state */
- enum_node = ast_data_add_node(tree, "state");
- if (!enum_node) {
- return -1;
- }
- ast_data_add_str(enum_node, "text", ast_state2str(ast_channel_state(chan)));
- ast_data_add_int(enum_node, "value", ast_channel_state(chan));
-
- /* hangupcause */
- enum_node = ast_data_add_node(tree, "hangupcause");
- if (!enum_node) {
- return -1;
- }
- ast_data_add_str(enum_node, "text", ast_cause2str(ast_channel_hangupcause(chan)));
- ast_data_add_int(enum_node, "value", ast_channel_hangupcause(chan));
-
- /* amaflags */
- enum_node = ast_data_add_node(tree, "amaflags");
- if (!enum_node) {
- return -1;
- }
- ast_data_add_str(enum_node, "text", ast_channel_amaflags2string(ast_channel_amaflags(chan)));
- ast_data_add_int(enum_node, "value", ast_channel_amaflags(chan));
-
- /* transfercapability */
- enum_node = ast_data_add_node(tree, "transfercapability");
- if (!enum_node) {
- return -1;
- }
- ast_data_add_str(enum_node, "text", ast_transfercapability2str(ast_channel_transfercapability(chan)));
- ast_data_add_int(enum_node, "value", ast_channel_transfercapability(chan));
-
- /* _softphangup */
- data_softhangup = ast_data_add_node(tree, "softhangup");
- if (!data_softhangup) {
- return -1;
- }
- ast_data_add_bool(data_softhangup, "dev", ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_DEV);
- ast_data_add_bool(data_softhangup, "asyncgoto", ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_ASYNCGOTO);
- ast_data_add_bool(data_softhangup, "shutdown", ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_SHUTDOWN);
- ast_data_add_bool(data_softhangup, "timeout", ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_TIMEOUT);
- ast_data_add_bool(data_softhangup, "appunload", ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_APPUNLOAD);
- ast_data_add_bool(data_softhangup, "explicit", ast_channel_softhangup_internal_flag(chan) & AST_SOFTHANGUP_EXPLICIT);
-
- /* channel flags */
- data_flags = ast_data_add_node(tree, "flags");
- if (!data_flags) {
- return -1;
- }
- channel_data_add_flags(data_flags, chan);
-
- ast_data_add_uint(tree, "timetohangup", ast_channel_whentohangup(chan)->tv_sec);
-
-#if 0 /* XXX AstData: ast_callerid no longer exists. (Equivalent code not readily apparent.) */
- /* callerid */
- data_callerid = ast_data_add_node(tree, "callerid");
- if (!data_callerid) {
- return -1;
- }
- ast_data_add_structure(ast_callerid, data_callerid, &(chan->cid));
- /* insert the callerid ton */
- enum_node = ast_data_add_node(data_callerid, "cid_ton");
- if (!enum_node) {
- return -1;
- }
- ast_data_add_int(enum_node, "value", chan->cid.cid_ton);
- snprintf(value_str, sizeof(value_str), "TON: %s/Plan: %s",
- party_number_ton2str(chan->cid.cid_ton),
- party_number_plan2str(chan->cid.cid_ton));
- ast_data_add_str(enum_node, "text", value_str);
-#endif
-
- /* tone zone */
- if (ast_channel_zone(chan)) {
- data_zones = ast_data_add_node(tree, "zone");
- if (!data_zones) {
- return -1;
- }
- ast_tone_zone_data_add_structure(data_zones, ast_channel_zone(chan));
- }
-
- /* insert cdr */
- data_cdr = ast_data_add_node(tree, "cdr");
- if (!data_cdr) {
- return -1;
- }
-
- return 0;
-}
-
-int ast_channel_data_cmp_structure(const struct ast_data_search *tree,
- struct ast_channel *chan, const char *structure_name)
-{
- return ast_data_search_cmp_structure(tree, ast_channel, chan, structure_name);
-}
-
/* ACCESSORS */
#define DEFINE_STRINGFIELD_SETTERS_FOR(field, publish, assert_on_null) \
diff --git a/main/codec_builtin.c b/main/codec_builtin.c
index 32ec12d3d..25aa51384 100644
--- a/main/codec_builtin.c
+++ b/main/codec_builtin.c
@@ -820,6 +820,13 @@ static struct ast_codec vp8 = {
.sample_rate = 1000,
};
+static struct ast_codec vp9 = {
+ .name = "vp9",
+ .description = "VP9 video",
+ .type = AST_MEDIA_TYPE_VIDEO,
+ .sample_rate = 1000,
+};
+
static struct ast_codec t140red = {
.name = "red",
.description = "T.140 Realtime Text with redundancy",
@@ -966,6 +973,7 @@ int ast_codec_builtin_init(void)
res |= CODEC_REGISTER_AND_CACHE(h264);
res |= CODEC_REGISTER_AND_CACHE(mpeg4);
res |= CODEC_REGISTER_AND_CACHE(vp8);
+ res |= CODEC_REGISTER_AND_CACHE(vp9);
res |= CODEC_REGISTER_AND_CACHE(t140red);
res |= CODEC_REGISTER_AND_CACHE(t140);
res |= CODEC_REGISTER_AND_CACHE(t38);
diff --git a/main/config.c b/main/config.c
index a3e09f67e..3d8dcfb3d 100644
--- a/main/config.c
+++ b/main/config.c
@@ -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;
diff --git a/main/config_options.c b/main/config_options.c
index c80777906..8eacbda35 100644
--- a/main/config_options.c
+++ b/main/config_options.c
@@ -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
diff --git a/main/core_local.c b/main/core_local.c
index aa232a4b6..23c7cce9d 100644
--- a/main/core_local.c
+++ b/main/core_local.c
@@ -233,43 +233,39 @@ struct local_pvt {
char exten[AST_MAX_EXTENSION];
};
-void ast_local_lock_all(struct ast_channel *chan, struct ast_channel **outchan,
- struct ast_channel **outowner)
+void ast_local_lock_all(struct ast_channel *chan, void **tech_pvt,
+ struct ast_channel **base_chan, struct ast_channel **base_owner)
{
struct local_pvt *p = ast_channel_tech_pvt(chan);
- *outchan = NULL;
- *outowner = NULL;
+ *tech_pvt = NULL;
+ *base_chan = NULL;
+ *base_owner = NULL;
if (p) {
- ao2_ref(p, 1);
- ast_unreal_lock_all(&p->base, outchan, outowner);
+ *tech_pvt = ao2_bump(p);
+ ast_unreal_lock_all(&p->base, base_chan, base_owner);
}
}
-void ast_local_unlock_all(struct ast_channel *chan)
+void ast_local_unlock_all(void *tech_pvt, struct ast_channel *base_chan,
+ struct ast_channel *base_owner)
{
- struct local_pvt *p = ast_channel_tech_pvt(chan);
- struct ast_unreal_pvt *base;
-
- if (!p) {
- return;
+ if (base_chan) {
+ ast_channel_unlock(base_chan);
+ ast_channel_unref(base_chan);
}
- base = &p->base;
-
- if (base->owner) {
- ast_channel_unlock(base->owner);
- ast_channel_unref(base->owner);
+ if (base_owner) {
+ ast_channel_unlock(base_owner);
+ ast_channel_unref(base_owner);
}
- if (base->chan) {
- ast_channel_unlock(base->chan);
- ast_channel_unref(base->chan);
+ if (tech_pvt) {
+ struct local_pvt *p = tech_pvt;
+ ao2_unlock(&p->base);
+ ao2_ref(tech_pvt, -1);
}
-
- ao2_unlock(base);
- ao2_ref(p, -1);
}
struct ast_channel *ast_local_get_peer(struct ast_channel *ast)
diff --git a/main/core_unreal.c b/main/core_unreal.c
index 5da740877..3db6a4dbd 100644
--- a/main/core_unreal.c
+++ b/main/core_unreal.c
@@ -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);
diff --git a/main/data.c b/main/data.c
deleted file mode 100644
index 15aca8b9d..000000000
--- a/main/data.c
+++ /dev/null
@@ -1,3346 +0,0 @@
-/*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2009, Eliel C. Sardanons (LU1ALY) <eliels@gmail.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
-
-/*! \file
- *
- * \brief Data retrieval API.
- *
- * \author Brett Bryant <brettbryant@gmail.com>
- * \author Eliel C. Sardanons (LU1ALY) <eliels@gmail.com>
- */
-
-/*** MODULEINFO
- <support_level>core</support_level>
- ***/
-
-#include "asterisk.h"
-
-#include "asterisk/_private.h"
-
-#include <regex.h>
-
-#include "asterisk/module.h"
-#include "asterisk/utils.h"
-#include "asterisk/lock.h"
-#include "asterisk/data.h"
-#include "asterisk/astobj2.h"
-#include "asterisk/xml.h"
-#include "asterisk/cli.h"
-#include "asterisk/term.h"
-#include "asterisk/manager.h"
-#include "asterisk/test.h"
-#include "asterisk/frame.h"
-#include "asterisk/codec.h"
-
-/*** DOCUMENTATION
- <manager name="DataGet" language="en_US">
- <synopsis>
- Retrieve the data api tree.
- </synopsis>
- <syntax>
- <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
- <parameter name="Path" required="true" />
- <parameter name="Search" />
- <parameter name="Filter" />
- </syntax>
- <description>
- <para>Retrieve the data api tree.</para>
- </description>
- </manager>
- ***/
-
-#define NUM_DATA_NODE_BUCKETS 59
-#define NUM_DATA_RESULT_BUCKETS 59
-#define NUM_DATA_SEARCH_BUCKETS 59
-#define NUM_DATA_FILTER_BUCKETS 59
-
-/*! \brief The last compatible version. */
-static const uint32_t latest_handler_compatible_version = 0;
-
-/*! \brief The last compatible version. */
-static const uint32_t latest_query_compatible_version = 0;
-
-/*! \brief Current handler structure version. */
-static const uint32_t current_handler_version = AST_DATA_HANDLER_VERSION;
-
-/*! \brief Current query structure version. */
-static const uint32_t current_query_version = AST_DATA_QUERY_VERSION;
-
-/*! \brief The data tree to be returned by the callbacks and
- managed by functions local to this file. */
-struct ast_data {
- enum ast_data_type type;
-
- /*! \brief The node content. */
- union {
- int32_t sint;
- uint32_t uint;
- double dbl;
- unsigned int boolean;
- char *str;
- char character;
- struct in_addr ipaddr;
- void *ptr;
- } payload;
-
- /*! \brief The filter node that depends on the current node,
- * this is used only when creating the result tree. */
- const struct data_filter *filter;
-
- /*! \brief The list of nodes inside this node. */
- struct ao2_container *children;
- /*! \brief The name of the node. */
- char name[0];
-};
-
-/*! \brief Type of comparisons allow in the search string. */
-enum data_search_comparison {
- DATA_CMP_UNKNOWN,
- DATA_CMP_EQ, /* = */
- DATA_CMP_NEQ, /* != */
- DATA_CMP_GT, /* > */
- DATA_CMP_GE, /* >= */
- DATA_CMP_LT, /* < */
- DATA_CMP_LE /* <= */
-};
-
-/*! \brief The list of nodes with their search requirement. */
-struct ast_data_search {
- /*! \brief The value of the comparison. */
- char *value;
- /*! \brief The type of comparison. */
- enum data_search_comparison cmp_type;
- /*! \brief reference another node. */
- struct ao2_container *children;
- /*! \brief The name of the node we are trying to compare. */
- char name[0];
-};
-
-struct data_filter;
-
-/*! \brief The filter node. */
-struct data_filter {
- /*! \brief node childrens. */
- struct ao2_container *children;
- /*! \brief glob list */
- AST_LIST_HEAD_NOLOCK(glob_list_t, data_filter) glob_list;
- /*! \brief glob list entry */
- AST_LIST_ENTRY(data_filter) list;
- /*! \brief node name. */
- char name[0];
-};
-
-/*! \brief A data container node pointing to the registered handler. */
-struct data_provider {
- /*! \brief node content handler. */
- const struct ast_data_handler *handler;
- /*! \brief Module providing this handler. */
- struct ast_module *module;
- /*! \brief children nodes. */
- struct ao2_container *children;
- /*! \brief Who registered this node. */
- const char *registrar;
- /*! \brief Node name. */
- char name[0];
-};
-
-/*! \brief This structure is used by the iterator. */
-struct ast_data_iterator {
- /*! \brief The internal iterator. */
- struct ao2_iterator internal_iterator;
- /*! \brief The last returned node. */
- struct ast_data *last;
- /*! \brief The iterator pattern. */
- const char *pattern;
- /*! \brief The compiled patter. */
- regex_t regex_pattern;
- /*! \brief is a regular expression. */
- unsigned int is_pattern:1;
-};
-
-struct {
- /*! \brief The asterisk data main content structure. */
- struct ao2_container *container;
- /*! \brief asterisk data locking mechanism. */
- ast_rwlock_t lock;
-} root_data;
-
-static void __data_result_print_cli(int fd, const struct ast_data *root, uint32_t depth);
-
-/*!
- * \internal
- * \brief Common string hash function.
- * \see ast_data_init
- */
-static int data_provider_hash(const void *obj, const int flags)
-{
- const struct data_provider *node = obj;
- return ast_str_case_hash(node->name);
-}
-
-/*!
- * \internal
- * \brief Compare two data_provider's.
- * \see ast_data_init
- */
-static int data_provider_cmp(void *obj1, void *obj2, int flags)
-{
- struct data_provider *node1 = obj1, *node2 = obj2;
- return strcasecmp(node1->name, node2->name) ? 0 : CMP_MATCH;
-}
-
-/*!
- * \internal
- * \brief Common string hash function for data nodes
- */
-static int data_result_hash(const void *obj, const int flags)
-{
- const struct ast_data *node = obj;
- return ast_str_hash(node->name);
-}
-
-/*!
- * \internal
- * \brief Common string comparison function
- */
-static int data_result_cmp(void *obj, void *arg, int flags)
-{
- struct ast_data *node1 = obj, *node2 = arg;
- return strcasecmp(node1->name, node2->name) ? 0 : CMP_MATCH;
-}
-
-/*!
- * \internal
- * \brief Lock the data registered handlers structure for writing.
- * \see data_unlock
- */
-#define data_write_lock() ast_rwlock_wrlock(&root_data.lock)
-
-/*!
- * \internal
- * \brief Lock the data registered handlers structure for reading.
- * \see data_unlock
- */
-#define data_read_lock() ast_rwlock_rdlock(&root_data.lock)
-
-/*!
- * \internal
- * \brief Unlock the data registered handlers structure.
- */
-#define data_unlock() ast_rwlock_unlock(&root_data.lock)
-
-/*!
- * \internal
- * \brief Check if a version is compatible with the current core.
- * \param[in] structure_version The current structure version.
- * \param[in] latest_compatible The latest compatible version.
- * \param[in] current The current Data API version.
- * \retval 1 If the module is compatible.
- * \retval 0 If the module is NOT compatible.
- */
-static int data_structure_compatible(int structure_version, uint32_t latest_compatible,
- uint32_t current)
-{
- if (structure_version >= latest_compatible && structure_version <= current) {
- return 1;
- }
-
- ast_log(LOG_ERROR, "A module is not compatible with the"
- "current data api version\n");
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief Get the next node name in a path (/node1/node2)
- * Avoid null nodes like //node1//node2/node3.
- * \param[in] path The path where we are going to search for the next node name.
- * \retval The next node name we found inside the given path.
- * \retval NULL if there are no more node names.
- */
-static char *next_node_name(char **path)
-{
- char *res;
-
- do {
- res = strsep(path, "/");
- } while (res && ast_strlen_zero(res));
-
- return res;
-}
-
-/*!
- * \internal
- * \brief Release the memory allocated by a call to ao2_alloc.
- */
-static void data_provider_destructor(void *obj)
-{
- struct data_provider *provider = obj;
-
- ao2_ref(provider->children, -1);
-}
-
-/*!
- * \internal
- * \brief Create a new data node.
- * \param[in] name The name of the node we are going to create.
- * \param[in] handler The handler registered for this node.
- * \param[in] registrar The name of the registrar.
- * \retval NULL on error.
- * \retval The allocated data node structure.
- */
-static struct data_provider *data_provider_new(const char *name,
- const struct ast_data_handler *handler, const char *registrar)
-{
- struct data_provider *node;
- size_t namelen;
-
- namelen = strlen(name) + 1;
-
- node = ao2_alloc(sizeof(*node) + namelen, data_provider_destructor);
- if (!node) {
- return NULL;
- }
-
- node->handler = handler;
- node->registrar = registrar;
- strcpy(node->name, name);
-
- /* initialize the childrens container. */
- if (!(node->children = ao2_container_alloc(NUM_DATA_NODE_BUCKETS,
- data_provider_hash, data_provider_cmp))) {
- ao2_ref(node, -1);
- return NULL;
- }
-
- return node;
-}
-
-/*!
- * \internal
- * \brief Add a child node named 'name' to the 'parent' node.
- * \param[in] parent Where to add the child node.
- * \param[in] name The name of the child node.
- * \param[in] handler The handler structure.
- * \param[in] registrar Who registered this node.
- * \retval NULL on error.
- * \retval A newly allocated child in parent.
- */
-static struct data_provider *data_provider_add_child(struct ao2_container *parent,
- const char *name, const struct ast_data_handler *handler, const char *registrar)
-{
- struct data_provider *child;
-
- child = data_provider_new(name, handler, registrar);
- if (!child) {
- return NULL;
- }
-
- ao2_link(parent, child);
-
- return child;
-}
-
-/*!
- * \internal
- * \brief Find a child node, based on his name.
- * \param[in] parent Where to find the node.
- * \param[in] name The node name to find.
- * \param[in] registrar Also check if the node was being used by this registrar.
- * \retval NULL if a node wasn't found.
- * \retval The node found.
- * \note Remember to decrement the ref count of the returned node after using it.
- */
-static struct data_provider *data_provider_find(struct ao2_container *parent,
- const char *name, const char *registrar)
-{
- struct data_provider *find_node, *found;
-
- /* XXX avoid allocating a new data node for searching... */
- find_node = data_provider_new(name, NULL, NULL);
- if (!find_node) {
- return NULL;
- }
-
- found = ao2_find(parent, find_node, OBJ_POINTER);
-
- /* free the created node used for searching. */
- ao2_ref(find_node, -1);
-
- if (found && found->registrar && registrar) {
- if (strcmp(found->registrar, registrar)) {
- /* if the name doesn't match, do not return this node. */
- ast_debug(1, "Registrar doesn't match, node was registered"
- " by '%s' and we are searching for '%s'\n",
- found->registrar, registrar);
- ao2_ref(found, -1);
- return NULL;
- }
- }
-
- return found;
-}
-
-/*!
- * \internal
- * \brief Release a group of nodes.
- * \param[in] parent The parent node.
- * \param[in] path The path of nodes to release.
- * \param[in] registrar Who registered this node.
- * \retval <0 on error.
- * \retval 0 on success.
- * \see data_provider_create
- */
-static int data_provider_release(struct ao2_container *parent, const char *path,
- const char *registrar)
-{
- char *node_name, *rpath;
- struct data_provider *child;
- int ret = 0;
-
- rpath = ast_strdupa(path);
-
- node_name = next_node_name(&rpath);
- if (!node_name) {
- return -1;
- }
-
- child = data_provider_find(parent, node_name, registrar);
- if (!child) {
- return -1;
- }
-
- /* if this is not a terminal node. */
- if (!child->handler && rpath) {
- ret = data_provider_release(child->children, rpath, registrar);
- }
-
- /* if this node is empty, unlink it. */
- if (!ret && !ao2_container_count(child->children)) {
- ao2_unlink(parent, child);
- }
-
- ao2_ref(child, -1);
-
- return ret;
-}
-
-/*!
- * \internal
- * \brief Release every node registered by 'registrar'.
- * \param[in] parent The parent node.
- * \param[in] registrar
- * \see __ast_data_unregister
- */
-static void data_provider_release_all(struct ao2_container *parent,
- const char *registrar)
-{
- struct ao2_iterator i;
- struct data_provider *node;
-
- i = ao2_iterator_init(parent, 0);
- while ((node = ao2_iterator_next(&i))) {
- if (!node->handler) {
- /* this is a non-terminal node, go inside it. */
- data_provider_release_all(node->children, registrar);
- if (!ao2_container_count(node->children)) {
- /* if this node was left empty, unlink it. */
- ao2_unlink(parent, node);
- }
- } else {
- if (!strcmp(node->registrar, registrar)) {
- /* if the registrars match, release it! */
- ao2_unlink(parent, node);
- }
- }
- ao2_ref(node, -1);
- }
- ao2_iterator_destroy(&i);
-
-}
-
-/*!
- * \internal
- * \brief Create the middle nodes for the specified path (asterisk/testnode1/childnode)
- * \param[in] parent Where to add the middle nodes structure.
- * \param[in] path The path of nodes to add.
- * \param[in] registrar Who is trying to create this node provider.
- * \retval NULL on error.
- * \retval The created node.
- * \see data_provider_release
- */
-static struct data_provider *data_provider_create(struct ao2_container *parent,
- const char *path, const char *registrar)
-{
- char *rpath, *node_name;
- struct data_provider *child, *ret = NULL;
-
- rpath = ast_strdupa(path);
-
- node_name = next_node_name(&rpath);
- if (!node_name) {
- /* no more nodes to create. */
- return NULL;
- }
-
- child = data_provider_find(parent, node_name, NULL);
-
- if (!child) {
- /* nodes without handler are non-terminal nodes. */
- child = data_provider_add_child(parent, node_name, NULL, registrar);
- }
-
- if (rpath) {
- ret = data_provider_create(child->children, rpath, registrar);
- if (ret) {
- ao2_ref(child, -1);
- }
- }
-
- return ret ? ret : child;
-}
-
-int __ast_data_register(const char *path, const struct ast_data_handler *handler,
- const char *registrar, struct ast_module *mod)
-{
- struct data_provider *node;
-
- if (!path) {
- return -1;
- }
-
- /* check if the handler structure is compatible. */
- if (!data_structure_compatible(handler->version,
- latest_handler_compatible_version,
- current_handler_version)) {
- return -1;
- }
-
- /* create the node structure for the registered handler. */
- data_write_lock();
-
- node = data_provider_create(root_data.container, path, registrar);
- if (!node) {
- ast_log(LOG_ERROR, "Unable to create the specified path (%s) "
- "for '%s'.\n", path, registrar);
- data_unlock();
- return -1;
- }
-
- if (ao2_container_count(node->children) || node->handler) {
- ast_log(LOG_ERROR, "The node '%s' was already registered. "
- "We were unable to register '%s' for registrar '%s'.\n",
- node->name, path, registrar);
- ao2_ref(node, -1);
- data_unlock();
- return -1;
- }
-
- /* add handler to that node. */
- node->handler = handler;
- node->module = mod;
-
- ao2_ref(node, -1);
-
- data_unlock();
-
- return 0;
-}
-
-int __ast_data_register_multiple(const struct ast_data_entry *data_entries,
- size_t entries, const char *registrar, struct ast_module *mod)
-{
- int i, res;
-
- for (i = 0; i < entries; i++) {
- res = __ast_data_register(data_entries[i].path, data_entries[i].handler,
- registrar, mod);
- if (res) {
- /* unregister all the already registered nodes, and make
- * this an atomic action. */
- while ((--i) >= 0) {
- __ast_data_unregister(data_entries[i].path, registrar);
- }
- return -1;
- }
- }
-
- return 0;
-}
-
-int __ast_data_unregister(const char *path, const char *registrar)
-{
- int ret = 0;
-
- data_write_lock();
- if (path) {
- ret = data_provider_release(root_data.container, path, registrar);
- } else {
- data_provider_release_all(root_data.container, registrar);
- }
- data_unlock();
-
- if (path && ret) {
- ast_log(LOG_ERROR, "Unable to unregister '%s' for '%s'\n",
- path, registrar);
- }
-
- return ret;
-}
-
-/*!
- * \internal
- * \brief Is a char used to specify a comparison?
- * \param[in] a Character to evaluate.
- * \retval 1 It is a char used to specify a comparison.
- * \retval 0 It is NOT a char used to specify a comparison.
- */
-static int data_search_comparison_char(char a)
-{
- switch (a) {
- case '!':
- case '=':
- case '<':
- case '>':
- return 1;
- }
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief Get the type of comparison.
- */
-static enum data_search_comparison data_search_comparison_type(const char *comparison)
-{
- if (!strcmp(comparison, "=")) {
- return DATA_CMP_EQ;
- } else if (!strcmp(comparison, "!=")) {
- return DATA_CMP_NEQ;
- } else if (!strcmp(comparison, "<")) {
- return DATA_CMP_LT;
- } else if (!strcmp(comparison, ">")) {
- return DATA_CMP_GT;
- } else if (!strcmp(comparison, "<=")) {
- return DATA_CMP_LE;
- } else if (!strcmp(comparison, ">=")) {
- return DATA_CMP_GE;
- }
-
- return DATA_CMP_UNKNOWN;
-}
-
-/*!
- * \internal
- * \brief Common string hash function for data nodes
- */
-static int data_search_hash(const void *obj, const int flags)
-{
- const struct ast_data_search *node = obj;
- return ast_str_hash(node->name);
-}
-
-/*!
- * \internal
- * \brief Common string comparison function
- */
-static int data_search_cmp(void *obj, void *arg, int flags)
-{
- struct ast_data_search *node1 = obj, *node2 = arg;
- return strcasecmp(node1->name, node2->name) ? 0 : CMP_MATCH;
-}
-
-/*!
- * \internal
- * \brief Destroy the ao2 search node.
- */
-static void data_search_destructor(void *obj)
-{
- struct ast_data_search *node = obj;
-
- if (node->value) {
- ast_free(node->value);
- }
-
- ao2_ref(node->children, -1);
-}
-
-/*!
- * \internal
- * \brief Allocate a search node.
- * \retval NULL on error.
- * \retval non-NULL The allocated search node structure.
- */
-static struct ast_data_search *data_search_alloc(const char *name)
-{
- struct ast_data_search *res;
- size_t name_len = strlen(name) + 1;
-
- res = ao2_alloc(sizeof(*res) + name_len, data_search_destructor);
- if (!res) {
- return NULL;
- }
-
- res->children = ao2_container_alloc(NUM_DATA_SEARCH_BUCKETS, data_search_hash,
- data_search_cmp);
-
- if (!res->children) {
- ao2_ref(res, -1);
- return NULL;
- }
-
- strcpy(res->name, name);
-
- return res;
-}
-
-/*!
- * \internal
- * \brief Find a child node, based on his name.
- * \param[in] parent Where to find the node.
- * \param[in] name The node name to find.
- * \retval NULL if a node wasn't found.
- * \retval The node found.
- * \note Remember to decrement the ref count of the returned node after using it.
- */
-static struct ast_data_search *data_search_find(struct ao2_container *parent,
- const char *name)
-{
- struct ast_data_search *find_node, *found;
-
- find_node = data_search_alloc(name);
- if (!find_node) {
- return NULL;
- }
-
- found = ao2_find(parent, find_node, OBJ_POINTER);
-
- /* free the created node used for searching. */
- ao2_ref(find_node, -1);
-
- return found;
-}
-
-/*!
- * \internal
- * \brief Add a child node named 'name' to the 'parent' node.
- * \param[in] parent Where to add the child node.
- * \param[in] name The name of the child node.
- * \retval NULL on error.
- * \retval A newly allocated child in parent.
- */
-static struct ast_data_search *data_search_add_child(struct ao2_container *parent,
- const char *name)
-{
- struct ast_data_search *child;
-
- child = data_search_alloc(name);
- if (!child) {
- return NULL;
- }
-
- ao2_link(parent, child);
-
- return child;
-}
-
-/*!
- * \internal
- * \brief Create the middle nodes for the specified path (asterisk/testnode1/childnode)
- * \param[in] parent Where to add the middle nodes structure.
- * \param[in] path The path of nodes to add.
- * \retval NULL on error.
- * \retval The created node.
- */
-static struct ast_data_search *data_search_create(struct ao2_container *parent,
- const char *path)
-{
- char *rpath, *node_name;
- struct ast_data_search *child = NULL;
- struct ao2_container *current = parent;
-
- rpath = ast_strdupa(path);
-
- node_name = next_node_name(&rpath);
- while (node_name) {
- child = data_search_find(current, node_name);
- if (!child) {
- child = data_search_add_child(current, node_name);
- }
- ao2_ref(child, -1);
- current = child->children;
- node_name = next_node_name(&rpath);
- }
-
- return child;
-}
-
-/*!
- * \internal
- * \brief Allocate a tree with the search string parsed.
- * \param[in] search_string The search string.
- * \retval NULL on error.
- * \retval non-NULL A dynamically allocated search tree.
- */
-static struct ast_data_search *data_search_generate(const char *search_string)
-{
- struct ast_str *name, *value, *comparison;
- char *elements, *search_string_dup, *saveptr;
- int i;
- struct ast_data_search *root, *child;
- enum data_search_comparison cmp_type;
- size_t search_string_len;
-
- if (!search_string) {
- ast_log(LOG_ERROR, "You must pass a valid search string.\n");
- return NULL;
- }
-
- search_string_len = strlen(search_string);
-
- name = ast_str_create(search_string_len);
- if (!name) {
- return NULL;
- }
- value = ast_str_create(search_string_len);
- if (!value) {
- ast_free(name);
- return NULL;
- }
- comparison = ast_str_create(search_string_len);
- if (!comparison) {
- ast_free(name);
- ast_free(value);
- return NULL;
- }
-
- search_string_dup = ast_strdupa(search_string);
-
- /* Create the root node (just used as a container) */
- root = data_search_alloc("/");
- if (!root) {
- ast_free(name);
- ast_free(value);
- ast_free(comparison);
- return NULL;
- }
-
- for (elements = strtok_r(search_string_dup, ",", &saveptr); elements;
- elements = strtok_r(NULL, ",", &saveptr)) {
- /* Parse the name */
- ast_str_reset(name);
- for (i = 0; !data_search_comparison_char(elements[i]) &&
- elements[i]; i++) {
- ast_str_append(&name, 0, "%c", elements[i]);
- }
-
- /* check if the syntax is ok. */
- if (!data_search_comparison_char(elements[i])) {
- /* if this is the end of the string, then this is
- * an error! */
- ast_log(LOG_ERROR, "Invalid search string!\n");
- continue;
- }
-
- /* parse the comparison string. */
- ast_str_reset(comparison);
- for (; data_search_comparison_char(elements[i]) && elements[i]; i++) {
- ast_str_append(&comparison, 0, "%c", elements[i]);
- }
-
- /* parse the value string. */
- ast_str_reset(value);
- for (; elements[i]; i++) {
- ast_str_append(&value, 0, "%c", elements[i]);
- }
-
- cmp_type = data_search_comparison_type(ast_str_buffer(comparison));
- if (cmp_type == DATA_CMP_UNKNOWN) {
- ast_log(LOG_ERROR, "Invalid comparison '%s'\n",
- ast_str_buffer(comparison));
- continue;
- }
-
- /* add this node to the tree. */
- child = data_search_create(root->children, ast_str_buffer(name));
- if (child) {
- child->cmp_type = cmp_type;
- child->value = ast_strdup(ast_str_buffer(value));
- }
- }
-
- ast_free(name);
- ast_free(value);
- ast_free(comparison);
-
- return root;
-}
-
-/*!
- * \internal
- * \brief Release the allocated memory for the search tree.
- * \param[in] search The search tree root node.
- */
-static void data_search_release(struct ast_data_search *search)
-{
- ao2_ref(search, -1);
-}
-
-/*!
- * \internal
- * \brief Based on the kind of comparison and the result in cmpval, return
- * if it matches.
- * \param[in] cmpval A result returned by a strcmp() for example.
- * \param[in] comparison_type The kind of comparison (<,>,=,!=,...)
- * \retval 1 If the comparison doesn't match.
- * \retval 0 If the comparison matches.
- */
-static inline int data_search_comparison_result(int cmpval,
- enum data_search_comparison comparison_type)
-{
- switch (comparison_type) {
- case DATA_CMP_GE:
- if (cmpval >= 0) {
- return 0;
- }
- break;
- case DATA_CMP_LE:
- if (cmpval <= 0) {
- return 0;
- }
- break;
- case DATA_CMP_EQ:
- if (cmpval == 0) {
- return 0;
- }
- break;
- case DATA_CMP_NEQ:
- if (cmpval != 0) {
- return 0;
- }
- break;
- case DATA_CMP_LT:
- if (cmpval < 0) {
- return 0;
- }
- break;
- case DATA_CMP_GT:
- if (cmpval > 0) {
- return 0;
- }
- break;
- case DATA_CMP_UNKNOWN:
- break;
- }
- return 1;
-}
-
-/*!
- * \internal
- * \brief Get an internal node, from the search tree.
- * \param[in] node A node container.
- * \param[in] path The path to the needed internal node.
- * \retval NULL if the internal node is not found.
- * \retval non-NULL the internal node with path 'path'.
- */
-static struct ast_data_search *data_search_get_node(const struct ast_data_search *node,
- const char *path)
-{
- char *savepath, *node_name;
- struct ast_data_search *child, *current = (struct ast_data_search *) node;
-
- if (!node) {
- return NULL;
- }
-
- savepath = ast_strdupa(path);
- node_name = next_node_name(&savepath);
-
- while (node_name) {
- child = data_search_find(current->children, node_name);
- if (current != node) {
- ao2_ref(current, -1);
- }
- if (!child) {
- return NULL;
- };
- current = child;
- node_name = next_node_name(&savepath);
- }
-
- return current;
-}
-
-/*!
- * \internal
- * \brief Based on a search tree, evaluate the specified 'name' inside the tree with the
- * current string value.
- * .search = "somename=somestring"
- * name = "somename"
- * value is the current value of something and will be evaluated against "somestring".
- * \param[in] root The root node pointer of the search tree.
- * \param[in] name The name of the specific.
- * \param[in] value The value to compare.
- * \returns The strcmp return value.
- */
-static int data_search_cmp_string(const struct ast_data_search *root, const char *name,
- char *value)
-{
- struct ast_data_search *child;
- enum data_search_comparison cmp_type;
- int ret;
-
- child = data_search_get_node(root, name);
- if (!child) {
- return 0;
- }
-
- ret = strcmp(value, child->value);
- cmp_type = child->cmp_type;
-
- ao2_ref(child, -1);
-
- return data_search_comparison_result(ret, cmp_type);
-}
-
-/*!
- * \internal
- * \brief Based on a search tree, evaluate the specified 'name' inside the tree with the
- * current pointer address value.
- * .search = "something=0x32323232"
- * name = "something"
- * value is the current value of something and will be evaluated against "0x32323232".
- * \param[in] root The root node pointer of the search tree.
- * \param[in] name The name of the specific.
- * \param[in] ptr The pointer address to compare.
- * \returns The (value - current_value) result.
- */
-static int data_search_cmp_ptr(const struct ast_data_search *root, const char *name,
- void *ptr)
-{
- struct ast_data_search *child;
- enum data_search_comparison cmp_type;
- void *node_ptr;
-
- child = data_search_get_node(root, name);
- if (!child) {
- return 0;
- }
-
- cmp_type = child->cmp_type;
-
- if (sscanf(child->value, "%p", &node_ptr) <= 0) {
- ao2_ref(child, -1);
- return 1;
- }
-
- ao2_ref(child, -1);
-
- return data_search_comparison_result((node_ptr - ptr), cmp_type);
-}
-
-/*!
- * \internal
- * \brief Based on a search tree, evaluate the specified 'name' inside the tree with the
- * current ipv4 address value.
- * .search = "something=192.168.2.2"
- * name = "something"
- * value is the current value of something and will be evaluated against "192.168.2.2".
- * \param[in] root The root node pointer of the search tree.
- * \param[in] name The name of the specific.
- * \param[in] addr The ipv4 address value to compare.
- * \returns The (value - current_value) result.
- */
-static int data_search_cmp_ipaddr(const struct ast_data_search *root, const char *name,
- struct in_addr addr)
-{
- struct ast_data_search *child;
- enum data_search_comparison cmp_type;
- struct in_addr node_addr;
-
- child = data_search_get_node(root, name);
- if (!child) {
- return 0;
- }
- cmp_type = child->cmp_type;
-
- inet_aton(child->value, &node_addr);
-
- ao2_ref(child, -1);
-
- return data_search_comparison_result((node_addr.s_addr - addr.s_addr), cmp_type);
-}
-
-/*!
- * \internal
- * \brief Based on a search tree, evaluate the specified 'name' inside the tree with the
- * current boolean value.
- * .search = "something=true"
- * name = "something"
- * value is the current value of something and will be evaluated against "true".
- * \param[in] root The root node pointer of the search tree.
- * \param[in] name The name of the specific.
- * \param[in] value The boolean value to compare.
- * \returns The (value - current_value) result.
- */
-static int data_search_cmp_bool(const struct ast_data_search *root, const char *name,
- unsigned int value)
-{
- struct ast_data_search *child;
- unsigned int node_value;
- enum data_search_comparison cmp_type;
-
- child = data_search_get_node(root, name);
- if (!child) {
- return 0;
- }
-
- node_value = abs(ast_true(child->value));
- cmp_type = child->cmp_type;
-
- ao2_ref(child, -1);
-
- return data_search_comparison_result(value - node_value, cmp_type);
-}
-
-/*!
- * \internal
- * \brief Based on a search tree, evaluate the specified 'name' inside the tree with the
- * current double value.
- * .search = "something=222"
- * name = "something"
- * value is the current value of something and will be evaluated against "222".
- * \param[in] root The root node pointer of the search tree.
- * \param[in] name The name of the specific.
- * \param[in] value The double value to compare.
- * \returns The (value - current_value) result.
- */
-static int data_search_cmp_dbl(const struct ast_data_search *root, const char *name,
- double value)
-{
- struct ast_data_search *child;
- double node_value;
- enum data_search_comparison cmp_type;
-
- child = data_search_get_node(root, name);
- if (!child) {
- return 0;
- }
-
- node_value = strtod(child->value, NULL);
- cmp_type = child->cmp_type;
-
- ao2_ref(child, -1);
-
- return data_search_comparison_result(value - node_value, cmp_type);
-}
-
-/*!
- * \internal
- * \brief Based on a search tree, evaluate the specified 'name' inside the tree with the
- * current unsigned integer value.
- * .search = "something=10"
- * name = "something"
- * value is the current value of something and will be evaluated against "10".
- * \param[in] root The root node pointer of the search tree.
- * \param[in] name The name of the specific.
- * \param[in] value The unsigned value to compare.
- * \returns The strcmp return value.
- */
-static int data_search_cmp_uint(const struct ast_data_search *root, const char *name,
- unsigned int value)
-{
- struct ast_data_search *child;
- unsigned int node_value;
- enum data_search_comparison cmp_type;
-
- child = data_search_get_node(root, name);
- if (!child) {
- return 0;
- }
-
- node_value = atoi(child->value);
- cmp_type = child->cmp_type;
-
- ao2_ref(child, -1);
-
- return data_search_comparison_result(value - node_value, cmp_type);
-}
-
-/*!
- * \internal
- * \brief Based on a search tree, evaluate the specified 'name' inside the tree with the
- * current signed integer value.
- * .search = "something=10"
- * name = "something"
- * value is the current value of something and will be evaluated against "10".
- * \param[in] root The root node pointer of the search tree.
- * \param[in] name The name of the specific.
- * \param[in] value The value to compare.
- * \returns The strcmp return value.
- */
-static int data_search_cmp_int(const struct ast_data_search *root, const char *name,
- int value)
-{
- struct ast_data_search *child;
- int node_value;
- enum data_search_comparison cmp_type;
-
- child = data_search_get_node(root, name);
- if (!child) {
- return 0;
- }
-
- node_value = atoi(child->value);
- cmp_type = child->cmp_type;
-
- ao2_ref(child, -1);
-
- return data_search_comparison_result(value - node_value, cmp_type);
-}
-
-/*!
- * \internal
- * \brief Based on a search tree, evaluate the specified 'name' inside the tree with the
- * current character value.
- * .search = "something=c"
- * name = "something"
- * value is the current value of something and will be evaluated against "c".
- * \param[in] root The root node pointer of the search tree.
- * \param[in] name The name of the specific.
- * \param[in] value The boolean value to compare.
- * \returns The (value - current_value) result.
- */
-static int data_search_cmp_char(const struct ast_data_search *root, const char *name,
- char value)
-{
- struct ast_data_search *child;
- char node_value;
- enum data_search_comparison cmp_type;
-
- child = data_search_get_node(root, name);
- if (!child) {
- return 0;
- }
-
- node_value = *(child->value);
- cmp_type = child->cmp_type;
-
- ao2_ref(child, -1);
-
- return data_search_comparison_result(value - node_value, cmp_type);
-}
-
-/*!
- * \internal
- * \brief Get the member pointer, from a mapping structure, based on its name.
- * \XXX We will need to improve performance here!!.
- * \retval <0 if the member was not found.
- * \retval >=0 The member position in the mapping structure.
- */
-static inline int data_search_mapping_find(const struct ast_data_mapping_structure *map,
- size_t mapping_len,
- const char *member_name)
-{
- int i;
-
- for (i = 0; i < mapping_len; i++) {
- if (!strcmp(map[i].name, member_name)) {
- return i;
- }
- }
-
- return -1;
-}
-
-int __ast_data_search_cmp_structure(const struct ast_data_search *search,
- const struct ast_data_mapping_structure *mapping, size_t mapping_len,
- void *structure, const char *structure_name)
-{
- struct ao2_iterator i;
- struct ast_data_search *node, *struct_children;
- int member, notmatch = 0;
-
- if (!search) {
- return 0;
- }
-
- struct_children = data_search_get_node(search, structure_name);
- if (!struct_children) {
- return 0;
- }
-
- i = ao2_iterator_init(struct_children->children, 0);
- while ((node = ao2_iterator_next(&i))) {
- member = data_search_mapping_find(mapping, mapping_len, node->name);
- if (member < 0) {
- /* the structure member name doesn't match! */
- ao2_ref(node, -1);
- ao2_ref(struct_children, -1);
- ao2_iterator_destroy(&i);
- return 0;
- }
-
- notmatch = 0;
- switch (mapping[member].type) {
- case AST_DATA_PASSWORD:
- notmatch = data_search_cmp_string(struct_children,
- node->name,
- mapping[member].get.AST_DATA_PASSWORD(structure));
- break;
- case AST_DATA_TIMESTAMP:
- notmatch = data_search_cmp_uint(struct_children,
- node->name,
- mapping[member].get.AST_DATA_TIMESTAMP(structure));
- break;
- case AST_DATA_SECONDS:
- notmatch = data_search_cmp_uint(struct_children,
- node->name,
- mapping[member].get.AST_DATA_SECONDS(structure));
- break;
- case AST_DATA_MILLISECONDS:
- notmatch = data_search_cmp_uint(struct_children,
- node->name,
- mapping[member].get.AST_DATA_MILLISECONDS(structure));
- break;
- case AST_DATA_STRING:
- notmatch = data_search_cmp_string(struct_children,
- node->name,
- mapping[member].get.AST_DATA_STRING(structure));
- break;
- case AST_DATA_CHARACTER:
- notmatch = data_search_cmp_char(struct_children,
- node->name,
- mapping[member].get.AST_DATA_CHARACTER(structure));
- break;
- case AST_DATA_INTEGER:
- notmatch = data_search_cmp_int(struct_children,
- node->name,
- mapping[member].get.AST_DATA_INTEGER(structure));
- break;
- case AST_DATA_BOOLEAN:
- notmatch = data_search_cmp_bool(struct_children,
- node->name,
- mapping[member].get.AST_DATA_BOOLEAN(structure));
- break;
- case AST_DATA_UNSIGNED_INTEGER:
- notmatch = data_search_cmp_uint(struct_children,
- node->name,
- mapping[member].get.AST_DATA_UNSIGNED_INTEGER(structure));
- break;
- case AST_DATA_DOUBLE:
- notmatch = data_search_cmp_dbl(struct_children,
- node->name,
- mapping[member].get.AST_DATA_DOUBLE(structure));
- break;
- case AST_DATA_IPADDR:
- notmatch = data_search_cmp_ipaddr(struct_children,
- node->name,
- mapping[member].get.AST_DATA_IPADDR(structure));
- break;
- case AST_DATA_POINTER:
- notmatch = data_search_cmp_ptr(struct_children,
- node->name,
- mapping[member].get.AST_DATA_POINTER(structure));
- break;
- case AST_DATA_CONTAINER:
- break;
- }
-
- ao2_ref(node, -1);
- }
- ao2_iterator_destroy(&i);
-
- ao2_ref(struct_children, -1);
-
- return notmatch;
-}
-
-/*!
- * \internal
- * \brief Release the memory allocated by a call to ao2_alloc.
- */
-static void data_result_destructor(void *obj)
-{
- struct ast_data *root = obj;
-
- switch (root->type) {
- case AST_DATA_PASSWORD:
- case AST_DATA_STRING:
- ast_free(root->payload.str);
- ao2_ref(root->children, -1);
- break;
- case AST_DATA_POINTER:
- case AST_DATA_CHARACTER:
- case AST_DATA_CONTAINER:
- case AST_DATA_INTEGER:
- case AST_DATA_TIMESTAMP:
- case AST_DATA_SECONDS:
- case AST_DATA_MILLISECONDS:
- case AST_DATA_UNSIGNED_INTEGER:
- case AST_DATA_DOUBLE:
- case AST_DATA_BOOLEAN:
- case AST_DATA_IPADDR:
- ao2_ref(root->children, -1);
- break;
- }
-}
-
-static struct ast_data *data_result_create(const char *name)
-{
- struct ast_data *res;
- size_t namelen;
-
- namelen = ast_strlen_zero(name) ? 1 : strlen(name) + 1;
-
- res = ao2_alloc(sizeof(*res) + namelen, data_result_destructor);
- if (!res) {
- return NULL;
- }
-
- strcpy(res->name, namelen ? name : "");
-
- /* initialize the children container */
- res->children = ao2_container_alloc(NUM_DATA_RESULT_BUCKETS, data_result_hash,
- data_result_cmp);
- if (!res->children) {
- ao2_ref(res, -1);
- return NULL;
- }
-
- /* set this node as a container. */
- res->type = AST_DATA_CONTAINER;
-
- return res;
-}
-
-/*!
- * \internal
- * \brief Find a child node, based on its name.
- * \param[in] root The starting point.
- * \param[in] name The child name.
- * \retval NULL if the node wasn't found.
- * \retval non-NULL the node we were looking for.
- */
-static struct ast_data *data_result_find_child(struct ast_data *root, const char *name)
-{
- struct ast_data *found, *find_node;
-
- find_node = data_result_create(name);
- if (!find_node) {
- return NULL;
- }
-
- found = ao2_find(root->children, find_node, OBJ_POINTER);
-
- /* release the temporary created node used for searching. */
- ao2_ref(find_node, -1);
-
- return found;
-}
-
-int ast_data_search_match(const struct ast_data_search *search, struct ast_data *data)
-{
- struct ao2_iterator i, ii;
- struct ast_data_search *s, *s_child;
- struct ast_data *d_child;
- int notmatch = 1;
-
- if (!search) {
- return 1;
- }
-
- s_child = data_search_find(search->children, data->name);
- if (!s_child) {
- /* nothing to compare */
- ao2_ref(s_child, -1);
- return 1;
- }
-
- i = ao2_iterator_init(s_child->children, 0);
- while ((s = ao2_iterator_next(&i))) {
- if (!ao2_container_count(s->children)) {
- /* compare this search node with every data node */
- d_child = data_result_find_child(data, s->name);
- if (!d_child) {
- ao2_ref(s, -1);
- notmatch = 1;
- continue;
- }
-
- switch (d_child->type) {
- case AST_DATA_PASSWORD:
- case AST_DATA_STRING:
- notmatch = data_search_cmp_string(s_child, d_child->name,
- d_child->payload.str);
- break;
- case AST_DATA_CHARACTER:
- notmatch = data_search_cmp_char(s_child, d_child->name,
- d_child->payload.character);
- break;
- case AST_DATA_INTEGER:
- notmatch = data_search_cmp_int(s_child, d_child->name,
- d_child->payload.sint);
- break;
- case AST_DATA_BOOLEAN:
- notmatch = data_search_cmp_bool(s_child, d_child->name,
- d_child->payload.boolean);
- break;
- case AST_DATA_UNSIGNED_INTEGER:
- notmatch = data_search_cmp_uint(s_child, d_child->name,
- d_child->payload.uint);
- break;
- case AST_DATA_TIMESTAMP:
- case AST_DATA_SECONDS:
- case AST_DATA_MILLISECONDS:
- case AST_DATA_DOUBLE:
- notmatch = data_search_cmp_uint(s_child, d_child->name,
- d_child->payload.dbl);
- break;
- case AST_DATA_IPADDR:
- notmatch = data_search_cmp_ipaddr(s_child, d_child->name,
- d_child->payload.ipaddr);
- break;
- case AST_DATA_POINTER:
- notmatch = data_search_cmp_ptr(s_child, d_child->name,
- d_child->payload.ptr);
- break;
- case AST_DATA_CONTAINER:
- break;
- }
- ao2_ref(d_child, -1);
- } else {
- ii = ao2_iterator_init(data->children, 0);
- while ((d_child = ao2_iterator_next(&ii))) {
- if (strcmp(d_child->name, s->name)) {
- ao2_ref(d_child, -1);
- continue;
- }
- if (!(notmatch = !ast_data_search_match(s_child, d_child))) {
- /* do not continue if we have a match. */
- ao2_ref(d_child, -1);
- break;
- }
- ao2_ref(d_child, -1);
- }
- ao2_iterator_destroy(&ii);
- }
- ao2_ref(s, -1);
- if (notmatch) {
- /* do not continue if we don't have a match. */
- break;
- }
- }
- ao2_iterator_destroy(&i);
-
- ao2_ref(s_child, -1);
-
- return !notmatch;
-}
-
-/*!
- * \internal
- * \brief Get an internal node, from the result set.
- * \param[in] node A node container.
- * \param[in] path The path to the needed internal node.
- * \retval NULL if the internal node is not found.
- * \retval non-NULL the internal node with path 'path'.
- */
-static struct ast_data *data_result_get_node(struct ast_data *node,
- const char *path)
-{
- char *savepath, *node_name;
- struct ast_data *child, *current = node;
-
- savepath = ast_strdupa(path);
- node_name = next_node_name(&savepath);
-
- while (node_name) {
- child = data_result_find_child(current, node_name);
- if (current != node) {
- ao2_ref(current, -1);
- }
- if (!child) {
- return NULL;
- }
- current = child;
- node_name = next_node_name(&savepath);
- }
-
- /* do not increment the refcount of the returned object. */
- if (current != node) {
- ao2_ref(current, -1);
- }
-
- return current;
-}
-
-/*!
- * \internal
- * \brief Add a child to the specified root node.
- * \param[in] root The root node pointer.
- * \param[in] child The child to add to the root node.
- */
-static void data_result_add_child(struct ast_data *root, struct ast_data *child)
-{
- ao2_link(root->children, child);
-}
-
-/*!
- * \internal
- * \brief Common string hash function for data nodes
- */
-static int data_filter_hash(const void *obj, const int flags)
-{
- const struct data_filter *node = obj;
- return ast_str_hash(node->name);
-}
-
-/*!
- * \internal
- * \brief Common string comparison function
- */
-static int data_filter_cmp(void *obj, void *arg, int flags)
-{
- struct data_filter *node1 = obj, *node2 = arg;
- return strcasecmp(node1->name, node2->name) ? 0 : CMP_MATCH;
-}
-
-/*!
- * \internal
- * \brief Destroy a data filter tree.
- * \param[in] obj Data filter list to be destroyed.
- */
-static void data_filter_destructor(void *obj)
-{
- struct data_filter *filter = obj, *globres;
-
- while ((globres = AST_LIST_REMOVE_HEAD(&(filter->glob_list), list))) {
- ao2_ref(globres, -1);
- }
-
- ao2_ref(filter->children, -1);
-}
-
-/*!
- * \internal
- * \brief Allocate a filter node.
- * \retval NULL on error.
- * \retval non-NULL The allocated search node structure.
- */
-static struct data_filter *data_filter_alloc(const char *name)
-{
- char *globname, *token;
- struct data_filter *res, *globfilter;
- size_t name_len = strlen(name) + 1;
-
- res = ao2_alloc(sizeof(*res) + name_len, data_filter_destructor);
- if (!res) {
- return NULL;
- }
-
- res->children = ao2_container_alloc(NUM_DATA_FILTER_BUCKETS, data_filter_hash,
- data_filter_cmp);
-
- if (!res->children) {
- ao2_ref(res, -1);
- return NULL;
- }
-
- strcpy(res->name, name);
-
- if (strchr(res->name, '*')) {
- globname = ast_strdupa(res->name);
-
- while ((token = strsep(&globname, "*"))) {
- globfilter = data_filter_alloc(token);
- AST_LIST_INSERT_TAIL(&(res->glob_list), globfilter, list);
- }
- }
-
- return res;
-}
-
-/*!
- * \internal
- * \brief Release a filter tree.
- * \param[in] filter The filter tree root node.
- */
-static void data_filter_release(struct data_filter *filter)
-{
- ao2_ref(filter, -1);
-}
-
-/*!
- * \internal
- * \brief Find a child node, based on his name.
- * \param[in] parent Where to find the node.
- * \param[in] name The node name to find.
- * \retval NULL if a node wasn't found.
- * \retval The node found.
- * \note Remember to decrement the ref count of the returned node after using it.
- */
-static struct data_filter *data_filter_find(struct ao2_container *parent,
- const char *name)
-{
- int i, olend, orend, globfound;
- size_t name_len = strlen(name), glob_len;
- struct ao2_iterator iter;
- struct data_filter *find_node, *found, *globres;
-
- find_node = data_filter_alloc(name);
- if (!find_node) {
- return NULL;
- }
-
- found = ao2_find(parent, find_node, OBJ_POINTER);
-
- /* free the created node used for searching. */
- ao2_ref(find_node, -1);
-
- if (found) {
- return found;
- }
-
- iter = ao2_iterator_init(parent, 0);
- while ((found = ao2_iterator_next(&iter))) {
- if (!AST_LIST_EMPTY(&(found->glob_list))) {
- i = 0;
- globfound = 1;
-
- olend = ast_strlen_zero(AST_LIST_FIRST(&(found->glob_list))->name);
- orend = ast_strlen_zero(AST_LIST_LAST(&(found->glob_list))->name);
-
- AST_LIST_TRAVERSE(&(found->glob_list), globres, list) {
- if (!*globres->name) {
- continue;
- }
-
- glob_len = strlen(globres->name);
-
- if (!i && !olend) {
- if (strncasecmp(name, globres->name, glob_len)) {
- globfound = 0;
- break;
- }
-
- i += glob_len;
- continue;
- }
-
- for (globfound = 0; name_len - i >= glob_len; ++i) {
- if (!strncasecmp(name + i, globres->name, glob_len)) {
- globfound = 1;
- i += glob_len;
- break;
- }
- }
-
- if (!globfound) {
- break;
- }
- }
-
- if (globfound && (i == name_len || orend)) {
- ao2_iterator_destroy(&iter);
- return found;
- }
- }
-
- ao2_ref(found, -1);
- }
- ao2_iterator_destroy(&iter);
-
- return NULL;
-}
-
-/*!
- * \internal
- * \brief Add a child to the specified node.
- * \param[in] root The root node where to add the child.
- * \param[in] name The name of the node to add.
- * \note Remember to decrement the ref count after using the returned node.
- */
-static struct data_filter *data_filter_add_child(struct ao2_container *root,
- char *name)
-{
- struct data_filter *node;
-
- node = data_filter_find(root, name);
- if (node) {
- return node;
- }
-
- node = data_filter_alloc(name);
- if (!node) {
- return NULL;
- }
-
- ao2_link(root, node);
-
- return node;
-}
-
-/*!
- * \internal
- * \brief Add a node to a filter list from a path
- * \param[in] Filter list to add the path onto.
- * \param[in] The path to add into the filter list.
- * \retval NULL on error.
- * \retval non-NULL A tree with the wanted nodes.
- */
-static int data_filter_add_nodes(struct ao2_container *root, char *path)
-{
- struct data_filter *node;
- char *savepath, *saveptr, *token, *node_name;
- int ret = 0;
-
- if (!path) {
- return 0;
- }
-
- savepath = ast_strdupa(path);
-
- node_name = next_node_name(&savepath);
-
- if (!node_name) {
- return 0;
- }
-
- for (token = strtok_r(node_name, "|", &saveptr);
- token; token = strtok_r(NULL, "|", &saveptr)) {
- node = data_filter_add_child(root, token);
- if (!node) {
- continue;
- }
- data_filter_add_nodes(node->children, savepath);
- ret = 1;
- ao2_ref(node, -1);
- }
-
- return ret;
-}
-
-/*!
- * \internal
- * \brief Generate a filter list based on a filter string provided by the API user.
- * \param[in] A filter string to create a filter from.
- */
-static struct data_filter *data_filter_generate(const char *constfilter)
-{
- struct data_filter *filter = NULL;
- char *strfilter, *token, *saveptr;
- int node_added = 0;
-
- if (!constfilter) {
- return NULL;
- }
-
- strfilter = ast_strdupa(constfilter);
-
- filter = data_filter_alloc("/");
- if (!filter) {
- return NULL;
- }
-
- for (token = strtok_r(strfilter, ",", &saveptr); token;
- token = strtok_r(NULL, ",", &saveptr)) {
- node_added = data_filter_add_nodes(filter->children, token);
- }
-
- if (!node_added) {
- ao2_ref(filter, -1);
- return NULL;
- }
-
- return filter;
-}
-
-/*!
- * \internal
- * \brief Generate all the tree from a specified provider.
- * \param[in] query The query executed.
- * \param[in] root_provider The provider specified in the path of the query.
- * \param[in] parent_node_name The root node name.
- * \retval NULL on error.
- * \retval non-NULL The generated result tree.
- */
-static struct ast_data *data_result_generate_node(const struct ast_data_query *query,
- const struct data_provider *root_provider,
- const char *parent_node_name,
- const struct ast_data_search *search,
- const struct data_filter *filter)
-{
- struct ast_data *generated, *node;
- struct ao2_iterator i;
- struct data_provider *provider;
- struct ast_data_search *search_child = NULL;
- struct data_filter *filter_child;
-
- node = data_result_create(parent_node_name);
- if (!node) {
- ast_log(LOG_ERROR, "Unable to allocate '%s' node\n", parent_node_name);
- return NULL;
- }
-
- if (root_provider->module) {
- ast_module_ref(root_provider->module);
- }
-
- /* if this is a terminal node, just run the callback function. */
- if (root_provider->handler && root_provider->handler->get) {
- node->filter = filter;
- root_provider->handler->get(search, node);
- if (root_provider->module) {
- ast_module_unref(root_provider->module);
- }
- return node;
- }
-
- if (root_provider->module) {
- ast_module_unref(root_provider->module);
- }
-
- /* if this is not a terminal node, generate every child node. */
- i = ao2_iterator_init(root_provider->children, 0);
- while ((provider = ao2_iterator_next(&i))) {
- filter_child = NULL;
- generated = NULL;
-
- /* get the internal search node. */
- if (search) {
- search_child = data_search_find(search->children, provider->name);
- }
- /* get the internal filter node. */
- if (filter) {
- filter_child = data_filter_find(filter->children, provider->name);
- }
-
- if (!filter || filter_child) {
- /* only generate the internal node, if we have something to
- * generate based on the filtering string. */
- generated = data_result_generate_node(query, provider,
- provider->name,
- search_child, filter_child);
- }
-
- /* decrement the refcount of the internal search node. */
- if (search_child) {
- ao2_ref(search_child, -1);
- }
-
- /* decrement the refcount of the internal filter node. */
- if (filter_child) {
- ao2_ref(filter_child, -1);
- }
-
- if (generated) {
- data_result_add_child(node, generated);
- ao2_ref(generated, -1);
- }
-
- ao2_ref(provider, -1);
- }
- ao2_iterator_destroy(&i);
-
- return node;
-}
-
-/*!
- * \internal
- * \brief Generate a result tree based on a query.
- * \param[in] query The complete query structure.
- * \param[in] search_path The path to retrieve.
- * \retval NULL on error.
- * \retval non-NULL The generated data result.
- */
-static struct ast_data *data_result_generate(const struct ast_data_query *query,
- const char *search_path)
-{
- char *node_name, *tmp_path;
- struct data_provider *provider_child, *tmp_provider_child;
- struct ast_data *result, *result_filtered;
- struct ast_data_search *search = NULL, *search_child = NULL;
- struct data_filter *filter = NULL, *filter_child = NULL;
-
- if (!search_path) {
- /* generate all the trees?. */
- return NULL;
- }
-
- tmp_path = ast_strdupa(search_path);
-
- /* start searching the root node name */
- node_name = next_node_name(&tmp_path);
- if (!node_name) {
- return NULL;
- }
- provider_child = data_provider_find(root_data.container, node_name, NULL);
-
- /* continue with the rest of the path. */
- while (provider_child) {
- node_name = next_node_name(&tmp_path);
- if (!node_name) {
- break;
- }
-
- tmp_provider_child = data_provider_find(provider_child->children,
- node_name, NULL);
-
- /* release the reference from this child */
- ao2_ref(provider_child, -1);
-
- provider_child = tmp_provider_child;
- }
-
- if (!provider_child) {
- ast_log(LOG_ERROR, "Invalid path '%s', '%s' not found.\n",
- tmp_path, node_name);
- return NULL;
- }
-
- /* generate the search tree. */
- if (query->search) {
- search = data_search_generate(query->search);
- if (search) {
- search_child = data_search_find(search->children,
- provider_child->name);
- }
- }
-
- /* generate the filter tree. */
- if (query->filter) {
- filter = data_filter_generate(query->filter);
- if (filter) {
- filter_child = data_filter_find(filter->children,
- provider_child->name);
- }
- }
-
- result = data_result_generate_node(query, provider_child, provider_child->name,
- search_child, filter_child);
-
- /* release the requested provider. */
- ao2_ref(provider_child, -1);
-
- /* release the generated search tree. */
- if (search_child) {
- ao2_ref(search_child, -1);
- }
-
- if (filter_child) {
- ao2_ref(filter_child, -1);
- }
-
- if (search) {
- data_search_release(search);
- }
-
- result_filtered = result;
-
- /* release the generated filter tree. */
- if (filter) {
- data_filter_release(filter);
- }
-
- return result_filtered;
-}
-
-struct ast_data *ast_data_get(const struct ast_data_query *query)
-{
- struct ast_data *res;
-
- /* check compatibility */
- if (!data_structure_compatible(query->version, latest_query_compatible_version,
- current_query_version)) {
- return NULL;
- }
-
- data_read_lock();
- res = data_result_generate(query, query->path);
- data_unlock();
-
- if (!res) {
- ast_log(LOG_ERROR, "Unable to get data from %s\n", query->path);
- return NULL;
- }
-
- return res;
-}
-
-#ifdef HAVE_LIBXML2
-/*!
- * \internal
- * \brief Helper function to move an ast_data tree to xml.
- * \param[in] parent_data The initial ast_data node to be passed to xml.
- * \param[out] parent_xml The root node to insert the xml.
- */
-static void data_get_xml_add_child(struct ast_data *parent_data,
- struct ast_xml_node *parent_xml)
-{
- struct ao2_iterator i;
- struct ast_data *node;
- struct ast_xml_node *child_xml;
- char node_content[256];
-
- i = ao2_iterator_init(parent_data->children, 0);
- while ((node = ao2_iterator_next(&i))) {
- child_xml = ast_xml_new_node(node->name);
- if (!child_xml) {
- ao2_ref(node, -1);
- continue;
- }
-
- switch (node->type) {
- case AST_DATA_CONTAINER:
- data_get_xml_add_child(node, child_xml);
- break;
- case AST_DATA_PASSWORD:
- ast_xml_set_text(child_xml, node->payload.str);
- break;
- case AST_DATA_TIMESTAMP:
- snprintf(node_content, sizeof(node_content), "%u",
- node->payload.uint);
- ast_xml_set_text(child_xml, node_content);
- break;
- case AST_DATA_SECONDS:
- snprintf(node_content, sizeof(node_content), "%u",
- node->payload.uint);
- ast_xml_set_text(child_xml, node_content);
- break;
- case AST_DATA_MILLISECONDS:
- snprintf(node_content, sizeof(node_content), "%u",
- node->payload.uint);
- ast_xml_set_text(child_xml, node_content);
- break;
- case AST_DATA_STRING:
- ast_xml_set_text(child_xml, node->payload.str);
- break;
- case AST_DATA_CHARACTER:
- snprintf(node_content, sizeof(node_content), "%c",
- node->payload.character);
- ast_xml_set_text(child_xml, node_content);
- break;
- case AST_DATA_INTEGER:
- snprintf(node_content, sizeof(node_content), "%d",
- node->payload.sint);
- ast_xml_set_text(child_xml, node_content);
- break;
- case AST_DATA_UNSIGNED_INTEGER:
- snprintf(node_content, sizeof(node_content), "%u",
- node->payload.uint);
- ast_xml_set_text(child_xml, node_content);
- break;
- case AST_DATA_DOUBLE:
- snprintf(node_content, sizeof(node_content), "%f",
- node->payload.dbl);
- ast_xml_set_text(child_xml, node_content);
- break;
- case AST_DATA_BOOLEAN:
- if (node->payload.boolean) {
- ast_xml_set_text(child_xml, "true");
- } else {
- ast_xml_set_text(child_xml, "false");
- }
- break;
- case AST_DATA_POINTER:
- snprintf(node_content, sizeof(node_content), "%p",
- node->payload.ptr);
- ast_xml_set_text(child_xml, node_content);
- break;
- case AST_DATA_IPADDR:
- snprintf(node_content, sizeof(node_content), "%s",
- ast_inet_ntoa(node->payload.ipaddr));
- ast_xml_set_text(child_xml, node_content);
- break;
- }
- ast_xml_add_child(parent_xml, child_xml);
-
- ao2_ref(node, -1);
- }
- ao2_iterator_destroy(&i);
-
-}
-
-struct ast_xml_doc *ast_data_get_xml(const struct ast_data_query *query)
-{
- struct ast_xml_doc *doc;
- struct ast_xml_node *root;
- struct ast_data *res;
-
- res = ast_data_get(query);
- if (!res) {
- return NULL;
- }
-
- doc = ast_xml_new();
- if (!doc) {
- ast_data_free(res);
- return NULL;
- }
-
- root = ast_xml_new_node(res->name);
- if (!root) {
- ast_xml_close(doc);
- }
-
- ast_xml_set_root(doc, root);
-
- data_get_xml_add_child(res, root);
-
- ast_data_free(res);
-
- return doc;
-}
-#endif
-
-enum ast_data_type ast_data_retrieve_type(struct ast_data *node, const char *path)
-{
- struct ast_data *internal;
-
- internal = data_result_get_node(node, path);
- if (!internal) {
- return -1;
- }
-
- return internal->type;
-}
-
-char *ast_data_retrieve_name(struct ast_data *node)
-{
- return node->name;
-}
-
-/*!
- * \internal
- * \brief Insert a child node inside a passed parent node.
- * \param root Where we are going to insert the child node.
- * \param name The name of the child node to add.
- * \param type The type of content inside the child node.
- * \param ptr The actual content of the child node.
- * \retval NULL on error.
- * \retval non-NULL The added child node pointer.
- */
-static struct ast_data *__ast_data_add(struct ast_data *root, const char *name,
- enum ast_data_type type, void *ptr)
-{
- struct ast_data *node;
- struct data_filter *filter, *filter_child = NULL;
-
- if (!root || !root->children) {
- /* invalid data result node. */
- return NULL;
- }
-
- /* check if we need to add this node, based on the filter. */
- if (root->filter) {
- filter = data_filter_find(root->filter->children, name);
- if (!filter) {
- return NULL;
- }
- ao2_ref(filter, -1);
- }
-
- node = data_result_create(name);
- if (!node) {
- return NULL;
- }
-
- node->type = type;
-
- switch (type) {
- case AST_DATA_BOOLEAN:
- node->payload.boolean = *(unsigned int *) ptr;
- break;
- case AST_DATA_INTEGER:
- node->payload.sint = *(int *) ptr;
- break;
- case AST_DATA_TIMESTAMP:
- case AST_DATA_SECONDS:
- case AST_DATA_MILLISECONDS:
- case AST_DATA_UNSIGNED_INTEGER:
- node->payload.uint = *(unsigned int *) ptr;
- break;
- case AST_DATA_DOUBLE:
- node->payload.dbl = *(double *) ptr;
- break;
- case AST_DATA_PASSWORD:
- case AST_DATA_STRING:
- node->payload.str = (char *) ptr;
- break;
- case AST_DATA_CHARACTER:
- node->payload.character = *(char *) ptr;
- break;
- case AST_DATA_POINTER:
- node->payload.ptr = ptr;
- break;
- case AST_DATA_IPADDR:
- node->payload.ipaddr = *(struct in_addr *) ptr;
- break;
- case AST_DATA_CONTAINER:
- if (root->filter) {
- filter_child = data_filter_find(root->filter->children, name);
- if (filter_child) {
- /* do not increment the refcount because it is not neccesary. */
- ao2_ref(filter_child, -1);
- }
- }
- node->filter = filter_child;
- break;
- default:
- break;
- }
-
- data_result_add_child(root, node);
-
- ao2_ref(node, -1);
-
- return node;
-}
-
-struct ast_data *ast_data_add_node(struct ast_data *root, const char *name)
-{
- return __ast_data_add(root, name, AST_DATA_CONTAINER, NULL);
-}
-
-struct ast_data *ast_data_add_int(struct ast_data *root, const char *name, int value)
-{
- return __ast_data_add(root, name, AST_DATA_INTEGER, &value);
-}
-
-struct ast_data *ast_data_add_char(struct ast_data *root, const char *name, char value)
-{
- return __ast_data_add(root, name, AST_DATA_CHARACTER, &value);
-}
-
-struct ast_data *ast_data_add_uint(struct ast_data *root, const char *name,
- unsigned int value)
-{
- return __ast_data_add(root, name, AST_DATA_UNSIGNED_INTEGER, &value);
-}
-
-struct ast_data *ast_data_add_dbl(struct ast_data *root, const char *childname,
- double dbl)
-{
- return __ast_data_add(root, childname, AST_DATA_DOUBLE, &dbl);
-}
-
-struct ast_data *ast_data_add_bool(struct ast_data *root, const char *childname,
- unsigned int boolean)
-{
- return __ast_data_add(root, childname, AST_DATA_BOOLEAN, &boolean);
-}
-
-struct ast_data *ast_data_add_ipaddr(struct ast_data *root, const char *childname,
- struct in_addr addr)
-{
- return __ast_data_add(root, childname, AST_DATA_IPADDR, &addr);
-}
-
-struct ast_data *ast_data_add_ptr(struct ast_data *root, const char *childname,
- void *ptr)
-{
- return __ast_data_add(root, childname, AST_DATA_POINTER, ptr);
-}
-
-struct ast_data *ast_data_add_timestamp(struct ast_data *root, const char *childname,
- unsigned int timestamp)
-{
- return __ast_data_add(root, childname, AST_DATA_TIMESTAMP, &timestamp);
-}
-
-struct ast_data *ast_data_add_seconds(struct ast_data *root, const char *childname,
- unsigned int seconds)
-{
- return __ast_data_add(root, childname, AST_DATA_SECONDS, &seconds);
-}
-
-struct ast_data *ast_data_add_milliseconds(struct ast_data *root, const char *childname,
- unsigned int milliseconds)
-{
- return __ast_data_add(root, childname, AST_DATA_MILLISECONDS, &milliseconds);
-}
-
-struct ast_data *ast_data_add_password(struct ast_data *root, const char *childname,
- const char *value)
-{
- char *name;
- size_t namelen = 1 + (ast_strlen_zero(value) ? 0 : strlen(value));
- struct ast_data *res;
-
- if (!(name = ast_malloc(namelen))) {
- return NULL;
- }
-
- strcpy(name, (ast_strlen_zero(value) ? "" : value));
-
- res = __ast_data_add(root, childname, AST_DATA_PASSWORD, name);
- if (!res) {
- ast_free(name);
- }
-
- return res;
-}
-
-struct ast_data *ast_data_add_str(struct ast_data *root, const char *childname,
- const char *value)
-{
- char *name;
- size_t namelen = 1 + (ast_strlen_zero(value) ? 0 : strlen(value));
- struct ast_data *res;
-
- if (!(name = ast_malloc(namelen))) {
- return NULL;
- }
-
- strcpy(name, (ast_strlen_zero(value) ? "" : value));
-
- res = __ast_data_add(root, childname, AST_DATA_STRING, name);
- if (!res) {
- ast_free(name);
- }
-
- return res;
-}
-
-int __ast_data_add_structure(struct ast_data *root,
- const struct ast_data_mapping_structure *mapping, size_t mapping_len,
- void *structure)
-{
- int i;
-
- for (i = 0; i < mapping_len; i++) {
- switch (mapping[i].type) {
- case AST_DATA_INTEGER:
- ast_data_add_int(root, mapping[i].name,
- mapping[i].get.AST_DATA_INTEGER(structure));
- break;
- case AST_DATA_UNSIGNED_INTEGER:
- ast_data_add_uint(root, mapping[i].name,
- mapping[i].get.AST_DATA_UNSIGNED_INTEGER(structure));
- break;
- case AST_DATA_DOUBLE:
- ast_data_add_dbl(root, mapping[i].name,
- mapping[i].get.AST_DATA_DOUBLE(structure));
- break;
- case AST_DATA_BOOLEAN:
- ast_data_add_bool(root, mapping[i].name,
- mapping[i].get.AST_DATA_BOOLEAN(structure));
- break;
- case AST_DATA_PASSWORD:
- ast_data_add_password(root, mapping[i].name,
- mapping[i].get.AST_DATA_PASSWORD(structure));
- break;
- case AST_DATA_TIMESTAMP:
- ast_data_add_timestamp(root, mapping[i].name,
- mapping[i].get.AST_DATA_TIMESTAMP(structure));
- break;
- case AST_DATA_SECONDS:
- ast_data_add_seconds(root, mapping[i].name,
- mapping[i].get.AST_DATA_SECONDS(structure));
- break;
- case AST_DATA_MILLISECONDS:
- ast_data_add_milliseconds(root, mapping[i].name,
- mapping[i].get.AST_DATA_MILLISECONDS(structure));
- break;
- case AST_DATA_STRING:
- ast_data_add_str(root, mapping[i].name,
- mapping[i].get.AST_DATA_STRING(structure));
- break;
- case AST_DATA_CHARACTER:
- ast_data_add_char(root, mapping[i].name,
- mapping[i].get.AST_DATA_CHARACTER(structure));
- break;
- case AST_DATA_CONTAINER:
- break;
- case AST_DATA_IPADDR:
- ast_data_add_ipaddr(root, mapping[i].name,
- mapping[i].get.AST_DATA_IPADDR(structure));
- break;
- case AST_DATA_POINTER:
- ast_data_add_ptr(root, mapping[i].name,
- mapping[i].get.AST_DATA_POINTER(structure));
- break;
- }
- }
-
- return 0;
-}
-
-void ast_data_remove_node(struct ast_data *root, struct ast_data *child)
-{
- ao2_unlink(root->children, child);
-}
-
-void ast_data_free(struct ast_data *root)
-{
- /* destroy it, this will destroy all the internal nodes. */
- ao2_ref(root, -1);
-}
-
-struct ast_data_iterator *ast_data_iterator_init(struct ast_data *tree,
- const char *elements)
-{
- struct ast_data_iterator *iterator;
- struct ao2_iterator i;
- struct ast_data *internal = tree;
- char *path, *ptr = NULL;
-
- if (!elements) {
- return NULL;
- }
-
- /* tree is the node we want to use to iterate? or we are going
- * to iterate thow an internal node? */
- path = ast_strdupa(elements);
-
- ptr = strrchr(path, '/');
- if (ptr) {
- *ptr = '\0';
- internal = data_result_get_node(tree, path);
- if (!internal) {
- return NULL;
- }
- }
-
- iterator = ast_calloc(1, sizeof(*iterator));
- if (!iterator) {
- return NULL;
- }
-
- i = ao2_iterator_init(internal->children, 0);
-
- iterator->pattern = (ptr ? strrchr(elements, '/') + 1 : elements);
-
- /* is the last node a regular expression?, compile it! */
- if (!regcomp(&(iterator->regex_pattern), iterator->pattern,
- REG_EXTENDED | REG_NOSUB | REG_ICASE)) {
- iterator->is_pattern = 1;
- }
-
- iterator->internal_iterator = i;
-
- return iterator;
-}
-
-void ast_data_iterator_end(struct ast_data_iterator *iterator)
-{
- /* decrement the reference counter. */
- if (iterator->last) {
- ao2_ref(iterator->last, -1);
- }
-
- /* release the generated pattern. */
- if (iterator->is_pattern) {
- regfree(&(iterator->regex_pattern));
- }
-
- ao2_iterator_destroy(&(iterator->internal_iterator));
-
- ast_free(iterator);
- iterator = NULL;
-}
-
-struct ast_data *ast_data_iterator_next(struct ast_data_iterator *iterator)
-{
- struct ast_data *res;
-
- if (iterator->last) {
- /* release the last retrieved node reference. */
- ao2_ref(iterator->last, -1);
- }
-
- while ((res = ao2_iterator_next(&iterator->internal_iterator))) {
- /* if there is no node name pattern specified, return
- * the next node. */
- if (!iterator->pattern) {
- break;
- }
-
- /* if the pattern is a regular expression, check if this node
- * matches. */
- if (iterator->is_pattern && !regexec(&(iterator->regex_pattern),
- res->name, 0, NULL, 0)) {
- break;
- }
-
- /* if there is a pattern specified, check if this node matches
- * the wanted node names. */
- if (!iterator->is_pattern && (iterator->pattern &&
- !strcasecmp(res->name, iterator->pattern))) {
- break;
- }
-
- ao2_ref(res, -1);
- }
-
- iterator->last = res;
-
- return res;
-}
-
-int ast_data_retrieve(struct ast_data *tree, const char *path,
- struct ast_data_retrieve *content)
-{
- struct ast_data *node;
-
- if (!content) {
- return -1;
- }
-
- node = data_result_get_node(tree, path);
- if (!node) {
- ast_log(LOG_ERROR, "Invalid internal node %s\n", path);
- return -1;
- }
-
- content->type = node->type;
- switch (node->type) {
- case AST_DATA_STRING:
- content->value.AST_DATA_STRING = node->payload.str;
- break;
- case AST_DATA_PASSWORD:
- content->value.AST_DATA_PASSWORD = node->payload.str;
- break;
- case AST_DATA_TIMESTAMP:
- content->value.AST_DATA_TIMESTAMP = node->payload.uint;
- break;
- case AST_DATA_SECONDS:
- content->value.AST_DATA_SECONDS = node->payload.uint;
- break;
- case AST_DATA_MILLISECONDS:
- content->value.AST_DATA_MILLISECONDS = node->payload.uint;
- break;
- case AST_DATA_CHARACTER:
- content->value.AST_DATA_CHARACTER = node->payload.character;
- break;
- case AST_DATA_INTEGER:
- content->value.AST_DATA_INTEGER = node->payload.sint;
- break;
- case AST_DATA_UNSIGNED_INTEGER:
- content->value.AST_DATA_UNSIGNED_INTEGER = node->payload.uint;
- break;
- case AST_DATA_BOOLEAN:
- content->value.AST_DATA_BOOLEAN = node->payload.boolean;
- break;
- case AST_DATA_IPADDR:
- content->value.AST_DATA_IPADDR = node->payload.ipaddr;
- break;
- case AST_DATA_DOUBLE:
- content->value.AST_DATA_DOUBLE = node->payload.dbl;
- break;
- case AST_DATA_CONTAINER:
- break;
- case AST_DATA_POINTER:
- content->value.AST_DATA_POINTER = node->payload.ptr;
- break;
- }
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief One color for each node type.
- */
-static const struct {
- enum ast_data_type type;
- int color;
-} data_result_color[] = {
- { AST_DATA_STRING, COLOR_BLUE },
- { AST_DATA_PASSWORD, COLOR_BRBLUE },
- { AST_DATA_TIMESTAMP, COLOR_CYAN },
- { AST_DATA_SECONDS, COLOR_MAGENTA },
- { AST_DATA_MILLISECONDS, COLOR_BRMAGENTA },
- { AST_DATA_CHARACTER, COLOR_GRAY },
- { AST_DATA_INTEGER, COLOR_RED },
- { AST_DATA_UNSIGNED_INTEGER, COLOR_RED },
- { AST_DATA_DOUBLE, COLOR_RED },
- { AST_DATA_BOOLEAN, COLOR_BRRED },
- { AST_DATA_CONTAINER, COLOR_GREEN },
- { AST_DATA_IPADDR, COLOR_BROWN },
- { AST_DATA_POINTER, COLOR_YELLOW },
-};
-
-/*!
- * \internal
- * \brief Get the color configured for a specific node type.
- * \param[in] type The node type.
- * \returns The color specified for the passed type.
- */
-static int data_result_get_color(enum ast_data_type type)
-{
- int i;
- for (i = 0; i < ARRAY_LEN(data_result_color); i++) {
- if (data_result_color[i].type == type) {
- return data_result_color[i].color;
- }
- }
-
- return COLOR_BLUE;
-}
-
-/*!
- * \internal
- * \brief Print a node to the CLI.
- * \param[in] fd The CLI file descriptor.
- * \param[in] node The node to print.
- * \param[in] depth The actual node depth in the tree.
- */
-static void data_result_print_cli_node(int fd, const struct ast_data *node, uint32_t depth)
-{
- int i;
- struct ast_str *tabs, *output;
-
- tabs = ast_str_create(depth * 10 + 1);
- if (!tabs) {
- return;
- }
- ast_str_reset(tabs);
- for (i = 0; i < depth; i++) {
- ast_str_append(&tabs, 0, " ");
- }
-
- output = ast_str_create(20);
- if (!output) {
- ast_free(tabs);
- return;
- }
-
- ast_str_reset(output);
- ast_term_color_code(&output, data_result_get_color(node->type), 0);
-
- switch (node->type) {
- case AST_DATA_POINTER:
- ast_str_append(&output, 0, "%s%s: %p\n", ast_str_buffer(tabs),
- node->name, node->payload.ptr);
- break;
- case AST_DATA_PASSWORD:
- ast_str_append(&output, 0, "%s%s: \"%s\"\n",
- ast_str_buffer(tabs),
- node->name,
- node->payload.str);
- break;
- case AST_DATA_STRING:
- ast_str_append(&output, 0, "%s%s: \"%s\"\n",
- ast_str_buffer(tabs),
- node->name,
- node->payload.str);
- break;
- case AST_DATA_CHARACTER:
- ast_str_append(&output, 0, "%s%s: \'%c\'\n",
- ast_str_buffer(tabs),
- node->name,
- node->payload.character);
- break;
- case AST_DATA_CONTAINER:
- ast_str_append(&output, 0, "%s%s\n", ast_str_buffer(tabs),
- node->name);
- break;
- case AST_DATA_TIMESTAMP:
- ast_str_append(&output, 0, "%s%s: %u\n", ast_str_buffer(tabs),
- node->name,
- node->payload.uint);
- break;
- case AST_DATA_SECONDS:
- ast_str_append(&output, 0, "%s%s: %u\n", ast_str_buffer(tabs),
- node->name,
- node->payload.uint);
- break;
- case AST_DATA_MILLISECONDS:
- ast_str_append(&output, 0, "%s%s: %u\n", ast_str_buffer(tabs),
- node->name,
- node->payload.uint);
- break;
- case AST_DATA_INTEGER:
- ast_str_append(&output, 0, "%s%s: %d\n", ast_str_buffer(tabs),
- node->name,
- node->payload.sint);
- break;
- case AST_DATA_UNSIGNED_INTEGER:
- ast_str_append(&output, 0, "%s%s: %u\n", ast_str_buffer(tabs),
- node->name,
- node->payload.uint);
- break;
- case AST_DATA_DOUBLE:
- ast_str_append(&output, 0, "%s%s: %lf\n", ast_str_buffer(tabs),
- node->name,
- node->payload.dbl);
- break;
- case AST_DATA_BOOLEAN:
- ast_str_append(&output, 0, "%s%s: %s\n", ast_str_buffer(tabs),
- node->name,
- ((node->payload.boolean) ? "True" : "False"));
- break;
- case AST_DATA_IPADDR:
- ast_str_append(&output, 0, "%s%s: %s\n", ast_str_buffer(tabs),
- node->name,
- ast_inet_ntoa(node->payload.ipaddr));
- break;
- }
-
- ast_free(tabs);
-
- ast_term_color_code(&output, 0, 0);
-
- ast_cli(fd, "%s", ast_str_buffer(output));
-
- ast_free(output);
-
- if (node->type == AST_DATA_CONTAINER) {
- __data_result_print_cli(fd, node, depth + 1);
- }
-}
-
-/*!
- * \internal
- * \brief Print out an ast_data tree to the CLI.
- * \param[in] fd The CLI file descriptor.
- * \param[in] root The root node of the tree.
- * \param[in] depth Actual depth.
- */
-
-static void __data_result_print_cli(int fd, const struct ast_data *root, uint32_t depth)
-{
- struct ao2_iterator iter;
- struct ast_data *node;
-
- if (root->type == AST_DATA_CONTAINER) {
- iter = ao2_iterator_init(root->children, 0);
- while ((node = ao2_iterator_next(&iter))) {
- data_result_print_cli_node(fd, node, depth + 1);
- ao2_ref(node, -1);
- }
- ao2_iterator_destroy(&iter);
- } else {
- data_result_print_cli_node(fd, root, depth);
- }
-}
-
-/*!
- * \internal
- * \brief
- * \param[in] fd The CLI file descriptor.
- * \param[in] root The root node of the tree.
- */
-static void data_result_print_cli(int fd, const struct ast_data *root)
-{
- ast_cli(fd, COLORIZE_FMT "\n", COLORIZE(data_result_get_color(root->type), 0, root->name));
-
- __data_result_print_cli(fd, root, 0);
-
- ast_cli(fd, "\n");
-}
-
-/*!
- * \internal
- * \brief Handle the CLI command "data get".
- */
-static char *handle_cli_data_get(struct ast_cli_entry *e, int cmd,
- struct ast_cli_args *a)
-{
- struct ast_data_query query = {
- .version = AST_DATA_QUERY_VERSION
- };
- struct ast_data *tree;
-
- switch (cmd) {
- case CLI_INIT:
- e->command = "data get";
- e->usage = ""
- "Usage: data get <path> [<search> [<filter>]]\n"
- " Get the tree based on a path.\n";
- return NULL;
- case CLI_GENERATE:
- return NULL;
- }
-
- if (a->argc < e->args + 1) {
- return CLI_SHOWUSAGE;
- }
-
- query.path = (char *) a->argv[e->args];
-
- if (a->argc > e->args + 1) {
- query.search = (char *) a->argv[e->args + 1];
- }
-
- if (a->argc > e->args + 2) {
- query.filter = (char *) a->argv[e->args + 2];
- }
-
- tree = ast_data_get(&query);
- if (!tree) {
- return CLI_FAILURE;
- }
-
- data_result_print_cli(a->fd, tree);
-
- ast_data_free(tree);
-
- return CLI_SUCCESS;
-}
-
-/*!
- * \internal
- * \brief Print the list of data providers.
- * \param[in] fd The CLI file descriptor.
- * \param[in] name The last node visited name.
- * \param[in] container The childrens of the last node.
- * \param[in] path The path to the current node.
- */
-static void data_provider_print_cli(int fd, const char *name,
- struct ao2_container *container, struct ast_str *path)
-{
- struct ao2_iterator i;
- struct ast_str *current_path;
- struct data_provider *provider;
-
- current_path = ast_str_create(60);
- if (!current_path) {
- return;
- }
-
- ast_str_reset(current_path);
- if (path) {
- ast_str_set(&current_path, 0, "%s/%s", ast_str_buffer(path), name);
- } else {
- ast_str_set(&current_path, 0, "%s", name);
- }
-
- i = ao2_iterator_init(container, 0);
- while ((provider = ao2_iterator_next(&i))) {
- if (provider->handler) {
- /* terminal node, print it. */
- ast_cli(fd, "%s/%s (", ast_str_buffer(current_path),
- provider->name);
- if (provider->handler->get) {
- ast_cli(fd, "get");
- }
- ast_cli(fd, ") [%s]\n", provider->registrar);
- }
- data_provider_print_cli(fd, provider->name, provider->children,
- current_path);
- ao2_ref(provider, -1);
- }
- ao2_iterator_destroy(&i);
-
- ast_free(current_path);
-}
-
-/*!
- * \internal
- * \brief Handle CLI command "data show providers"
- */
-static char *handle_cli_data_show_providers(struct ast_cli_entry *e, int cmd,
- struct ast_cli_args *a)
-{
- switch (cmd) {
- case CLI_INIT:
- e->command = "data show providers";
- e->usage = ""
- "Usage: data show providers\n"
- " Show the list of registered providers\n";
- return NULL;
- case CLI_GENERATE:
- return NULL;
- }
-
- data_read_lock();
- data_provider_print_cli(a->fd, "", root_data.container, NULL);
- data_unlock();
-
- return CLI_SUCCESS;
-}
-
-/*!
- * \internal
- * \brief Data API CLI commands.
- */
-static struct ast_cli_entry cli_data[] = {
- AST_CLI_DEFINE(handle_cli_data_get, "Data API get"),
- AST_CLI_DEFINE(handle_cli_data_show_providers, "Show data providers")
-};
-
-/*!
- * \internal
- * \brief Output a tree to the AMI.
- * \param[in] s AMI session.
- * \param[in] name The root node name.
- * \param[in] container The root container.
- * \param[in] path The current path.
- */
-static void data_result_manager_output(struct mansession *s, const char *name,
- struct ao2_container *container, struct ast_str *path, int id)
-{
- struct ao2_iterator i;
- struct ast_str *current_path;
- struct ast_data *node;
- int current_id = id;
-
- current_path = ast_str_create(60);
- if (!current_path) {
- return;
- }
-
- ast_str_reset(current_path);
- if (path) {
- ast_str_set(&current_path, 0, "%s.%s", ast_str_buffer(path), name);
- } else {
- ast_str_set(&current_path, 0, "%s", name);
- }
-
- i = ao2_iterator_init(container, 0);
- while ((node = ao2_iterator_next(&i))) {
- /* terminal node, print it. */
- if (node->type != AST_DATA_CONTAINER) {
- astman_append(s, "%d-%s.%s", id, ast_str_buffer(current_path),
- node->name);
- }
- switch (node->type) {
- case AST_DATA_CONTAINER:
- data_result_manager_output(s, node->name, node->children, current_path, ++current_id);
- break;
- case AST_DATA_INTEGER:
- astman_append(s, ": %d\r\n", node->payload.sint);
- break;
- case AST_DATA_TIMESTAMP:
- case AST_DATA_SECONDS:
- case AST_DATA_MILLISECONDS:
- case AST_DATA_UNSIGNED_INTEGER:
- astman_append(s, ": %u\r\n", node->payload.uint);
- break;
- case AST_DATA_PASSWORD:
- astman_append(s, ": %s\r\n", node->payload.str);
- break;
- case AST_DATA_STRING:
- astman_append(s, ": %s\r\n", node->payload.str);
- break;
- case AST_DATA_CHARACTER:
- astman_append(s, ": %c\r\n", node->payload.character);
- break;
- case AST_DATA_IPADDR:
- astman_append(s, ": %s\r\n", ast_inet_ntoa(node->payload.ipaddr));
- break;
- case AST_DATA_POINTER:
- break;
- case AST_DATA_DOUBLE:
- astman_append(s, ": %f\r\n", node->payload.dbl);
- break;
- case AST_DATA_BOOLEAN:
- astman_append(s, ": %s\r\n",
- (node->payload.boolean ? "True" : "False"));
- break;
- }
-
- ao2_ref(node, -1);
- }
- ao2_iterator_destroy(&i);
-
- ast_free(current_path);
-}
-
-/*!
- * \internal
- * \brief Implements the manager action: "DataGet".
- */
-static int manager_data_get(struct mansession *s, const struct message *m)
-{
- const char *path = astman_get_header(m, "Path");
- const char *search = astman_get_header(m, "Search");
- const char *filter = astman_get_header(m, "Filter");
- const char *id = astman_get_header(m, "ActionID");
- struct ast_data *res;
- struct ast_data_query query = {
- .version = AST_DATA_QUERY_VERSION,
- .path = (char *) path,
- .search = (char *) search,
- .filter = (char *) filter,
- };
-
- if (ast_strlen_zero(path)) {
- astman_send_error(s, m, "'Path' parameter not specified");
- return 0;
- }
-
- res = ast_data_get(&query);
- if (!res) {
- astman_send_error(s, m, "No data returned");
- return 0;
- }
-
- astman_append(s, "Event: DataGet Tree\r\n");
- if (!ast_strlen_zero(id)) {
- astman_append(s, "ActionID: %s\r\n", id);
- }
- data_result_manager_output(s, res->name, res->children, NULL, 0);
- astman_append(s, "\r\n");
-
- ast_data_free(res);
-
- return RESULT_SUCCESS;
-}
-
-static int data_add_codec(struct ast_data *codecs, struct ast_format *format) {
- struct ast_data *codec;
- struct ast_codec *tmp;
-
- tmp = ast_codec_get_by_id(ast_format_get_codec_id(format));
- if (!tmp) {
- return -1;
- }
-
- codec = ast_data_add_node(codecs, "codec");
- if (!codec) {
- ao2_ref(tmp, -1);
- return -1;
- }
-
- ast_data_add_str(codec, "name", tmp->name);
- ast_data_add_int(codec, "samplespersecond", tmp->sample_rate);
- ast_data_add_str(codec, "description", tmp->description);
- ast_data_add_int(codec, "frame_length", tmp->minimum_bytes);
- ao2_ref(tmp, -1);
-
- return 0;
-}
-
-int ast_data_add_codec(struct ast_data *root, const char *node_name, struct ast_format *format)
-{
- struct ast_data *codecs;
-
- codecs = ast_data_add_node(root, node_name);
- if (!codecs) {
- return -1;
- }
-
- return data_add_codec(codecs, format);
-}
-
-int ast_data_add_codecs(struct ast_data *root, const char *node_name, struct ast_format_cap *cap)
-{
- struct ast_data *codecs;
- size_t i;
- size_t count;
-
- codecs = ast_data_add_node(root, node_name);
- if (!codecs) {
- return -1;
- }
-
- count = ast_format_cap_count(cap);
- for (i = 0; i < count; ++i) {
- struct ast_format *fmt;
-
- fmt = ast_format_cap_get_format(cap, i);
- if (!fmt) {
- return -1;
- }
-
- if (data_add_codec(codecs, fmt)) {
- ao2_ref(fmt, -1);
- return -1;
- }
-
- ao2_ref(fmt, -1);
- }
-
- return 0;
-}
-
-#ifdef TEST_FRAMEWORK
-
-/*!
- * \internal
- * \brief Structure used to test how to add a complete structure,
- * and how to compare it.
- */
-struct test_structure {
- int a_int;
- unsigned int b_bool:1;
- char *c_str;
- unsigned int a_uint;
-};
-
-/*!
- * \internal
- * \brief test_structure mapping.
- */
-#define DATA_EXPORT_TEST_STRUCTURE(MEMBER) \
- MEMBER(test_structure, a_int, AST_DATA_INTEGER) \
- MEMBER(test_structure, b_bool, AST_DATA_BOOLEAN) \
- MEMBER(test_structure, c_str, AST_DATA_STRING) \
- MEMBER(test_structure, a_uint, AST_DATA_UNSIGNED_INTEGER)
-
-AST_DATA_STRUCTURE(test_structure, DATA_EXPORT_TEST_STRUCTURE);
-
-/*!
- * \internal
- * \brief Callback implementation.
- */
-static int test_data_full_provider(const struct ast_data_search *search,
- struct ast_data *root)
-{
- struct ast_data *test_structure;
- struct test_structure local_test_structure = {
- .a_int = 10,
- .b_bool = 1,
- .c_str = "test string",
- .a_uint = 20
- };
-
- test_structure = ast_data_add_node(root, "test_structure");
- if (!test_structure) {
- ast_debug(1, "Internal data api error\n");
- return 0;
- }
-
- /* add the complete structure. */
- ast_data_add_structure(test_structure, test_structure, &local_test_structure);
-
- if (!ast_data_search_match(search, test_structure)) {
- ast_data_remove_node(root, test_structure);
- }
-
- return 0;
-}
-
-/*!
- * \internal
- * \brief Handler definition for the full provider.
- */
-static const struct ast_data_handler full_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = test_data_full_provider
-};
-
-/*!
- * \internal
- * \brief Structure used to define multiple providers at once.
- */
-static const struct ast_data_entry test_providers[] = {
- AST_DATA_ENTRY("test/node1/node11/node111", &full_provider)
-};
-
-AST_TEST_DEFINE(test_data_get)
-{
- struct ast_data *res, *node;
- struct ast_data_iterator *i;
- struct ast_data_query query = {
- .version = AST_DATA_QUERY_VERSION,
- .path = "test/node1/node11/node111",
- .search = "node111/test_structure/a_int=10",
- .filter = "node111/test_structure/a*int"
- };
-
- switch (cmd) {
- case TEST_INIT:
- info->name = "data_test";
- info->category = "/main/data/";
- info->summary = "Data API unit test";
- info->description =
- "Tests whether data API get implementation works as expected.";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
-
- ast_data_register_multiple_core(test_providers, ARRAY_LEN(test_providers));
-
- res = ast_data_get(&query);
- if (!res) {
- ast_test_status_update(test, "Unable to get tree.");
- ast_data_unregister("test/node1/node11/node111");
- return AST_TEST_FAIL;
- }
-
- /* initiate the iterator and check for errors. */
- i = ast_data_iterator_init(res, "test_structure/");
- if (!i) {
- ast_test_status_update(test, "Unable to initiate the iterator.");
- ast_data_free(res);
- ast_data_unregister("test/node1/node11/node111");
- return AST_TEST_FAIL;
- }
-
- /* walk the returned nodes. */
- while ((node = ast_data_iterator_next(i))) {
- if (!strcmp(ast_data_retrieve_name(node), "a_int")) {
- if (ast_data_retrieve_int(node, "/") != 10) {
- ast_data_iterator_end(i);
- ast_data_free(res);
- ast_data_unregister("test/node1/node11/node111");
- return AST_TEST_FAIL;
- }
- } else if (!strcmp(ast_data_retrieve_name(node), "a_uint")) {
- if (ast_data_retrieve_uint(node, "/") != 20) {
- ast_data_iterator_end(i);
- ast_data_free(res);
- ast_data_unregister("test/node1/node11/node111");
- return AST_TEST_FAIL;
- }
- }
- }
-
- /* finish the iterator. */
- ast_data_iterator_end(i);
-
- ast_data_free(res);
-
- ast_data_unregister("test/node1/node11/node111");
-
- return AST_TEST_PASS;
-}
-
-#endif
-
-/*!
- * \internal
- * \brief Clean up resources on Asterisk shutdown
- */
-static void data_shutdown(void)
-{
- ast_manager_unregister("DataGet");
- ast_cli_unregister_multiple(cli_data, ARRAY_LEN(cli_data));
- ao2_t_ref(root_data.container, -1, "Unref root_data.container in data_shutdown");
- root_data.container = NULL;
- ast_rwlock_destroy(&root_data.lock);
- AST_TEST_UNREGISTER(test_data_get);
-}
-
-int ast_data_init(void)
-{
- int res = 0;
-
- ast_rwlock_init(&root_data.lock);
-
- if (!(root_data.container = ao2_container_alloc(NUM_DATA_NODE_BUCKETS,
- data_provider_hash, data_provider_cmp))) {
- return -1;
- }
-
- res |= ast_cli_register_multiple(cli_data, ARRAY_LEN(cli_data));
-
- res |= ast_manager_register_xml_core("DataGet", 0, manager_data_get);
-
- AST_TEST_REGISTER(test_data_get);
-
- ast_register_cleanup(data_shutdown);
-
- return res;
-}
diff --git a/main/format_cache.c b/main/format_cache.c
index 302bbf827..1a67ebe60 100644
--- a/main/format_cache.c
+++ b/main/format_cache.c
@@ -191,6 +191,11 @@ struct ast_format *ast_format_mp4;
struct ast_format *ast_format_vp8;
/*!
+ * \brief Built-in cached vp9 format.
+ */
+struct ast_format *ast_format_vp9;
+
+/*!
* \brief Built-in cached jpeg format.
*/
struct ast_format *ast_format_jpeg;
@@ -345,6 +350,7 @@ static void format_cache_shutdown(void)
ao2_replace(ast_format_h264, NULL);
ao2_replace(ast_format_mp4, NULL);
ao2_replace(ast_format_vp8, NULL);
+ ao2_replace(ast_format_vp9, NULL);
ao2_replace(ast_format_t140_red, NULL);
ao2_replace(ast_format_t140, NULL);
ao2_replace(ast_format_t38, NULL);
@@ -444,6 +450,8 @@ static void set_cached_format(const char *name, struct ast_format *format)
ao2_replace(ast_format_mp4, format);
} else if (!strcmp(name, "vp8")) {
ao2_replace(ast_format_vp8, format);
+ } else if (!strcmp(name, "vp9")) {
+ ao2_replace(ast_format_vp9, format);
} else if (!strcmp(name, "red")) {
ao2_replace(ast_format_t140_red, format);
} else if (!strcmp(name, "t140")) {
diff --git a/main/http.c b/main/http.c
index ea85a2823..7191eb524 100644
--- a/main/http.c
+++ b/main/http.c
@@ -508,7 +508,7 @@ void ast_http_send(struct ast_tcptls_session_instance *ser,
send_content = method != AST_HTTP_HEAD || status_code >= 400;
/* send http header */
- ast_iostream_printf(ser->stream,
+ if (ast_iostream_printf(ser->stream,
"HTTP/1.1 %d %s\r\n"
"%s"
"Date: %s\r\n"
@@ -526,13 +526,16 @@ void ast_http_send(struct ast_tcptls_session_instance *ser,
http_header ? ast_str_buffer(http_header) : "",
content_length,
send_content && out && ast_str_strlen(out) ? ast_str_buffer(out) : ""
- );
+ ) <= 0) {
+ ast_debug(1, "ast_iostream_printf() failed: %s\n", strerror(errno));
+ close_connection = 1;
+ }
/* send content */
- if (send_content && fd) {
+ if (!close_connection && send_content && fd) {
while ((len = read(fd, buf, sizeof(buf))) > 0) {
if (ast_iostream_write(ser->stream, buf, len) != len) {
- ast_log(LOG_WARNING, "fwrite() failed: %s\n", strerror(errno));
+ ast_debug(1, "ast_iostream_write() failed: %s\n", strerror(errno));
close_connection = 1;
break;
}
diff --git a/main/indications.c b/main/indications.c
index 0af6668cf..8940a37b0 100644
--- a/main/indications.c
+++ b/main/indications.c
@@ -41,23 +41,9 @@
#include "asterisk/cli.h"
#include "asterisk/module.h"
#include "asterisk/astobj2.h"
-#include "asterisk/data.h"
#include "asterisk/_private.h" /* _init(), _reload() */
-#define DATA_EXPORT_TONE_ZONE(MEMBER) \
- MEMBER(ast_tone_zone, country, AST_DATA_STRING) \
- MEMBER(ast_tone_zone, description, AST_DATA_STRING) \
- MEMBER(ast_tone_zone, nrringcadence, AST_DATA_UNSIGNED_INTEGER)
-
-AST_DATA_STRUCTURE(ast_tone_zone, DATA_EXPORT_TONE_ZONE);
-
-#define DATA_EXPORT_TONE_ZONE_SOUND(MEMBER) \
- MEMBER(ast_tone_zone_sound, name, AST_DATA_STRING) \
- MEMBER(ast_tone_zone_sound, data, AST_DATA_STRING)
-
-AST_DATA_STRUCTURE(ast_tone_zone_sound, DATA_EXPORT_TONE_ZONE_SOUND);
-
/* Globals */
static const char config[] = "indications.conf";
@@ -1124,33 +1110,6 @@ static int ast_tone_zone_cmp(void *obj, void *arg, int flags)
CMP_MATCH | CMP_STOP : 0;
}
-int ast_tone_zone_data_add_structure(struct ast_data *tree, struct ast_tone_zone *zone)
-{
- struct ast_data *data_zone_sound;
- struct ast_tone_zone_sound *s;
-
- ast_data_add_structure(ast_tone_zone, tree, zone);
-
- if (AST_LIST_EMPTY(&zone->tones)) {
- return 0;
- }
-
- data_zone_sound = ast_data_add_node(tree, "tones");
- if (!data_zone_sound) {
- return -1;
- }
-
- ast_tone_zone_lock(zone);
-
- AST_LIST_TRAVERSE(&zone->tones, s, entry) {
- ast_data_add_structure(ast_tone_zone_sound, data_zone_sound, s);
- }
-
- ast_tone_zone_unlock(zone);
-
- return 0;
-}
-
/*!
* \internal
* \brief Clean up resources on Asterisk shutdown
diff --git a/main/json.c b/main/json.c
index a28dbb2e2..9004978b4 100644
--- a/main/json.c
+++ b/main/json.c
@@ -823,6 +823,7 @@ struct ast_json *ast_json_vpack(char const *format, va_list ap)
ast_log(LOG_ERROR,
"Error building JSON from '%s': %s.\n",
format, error.text);
+ ast_log_backtrace();
}
}
return r;
diff --git a/main/pbx.c b/main/pbx.c
index ccfba054e..2366b72b0 100644
--- a/main/pbx.c
+++ b/main/pbx.c
@@ -8306,56 +8306,6 @@ static void presence_state_cb(void *unused, struct stasis_subscription *sub, str
ast_free(hint_app);
}
-/*!
- * \internal
- * \brief Implements the hints data provider.
- */
-static int hints_data_provider_get(const struct ast_data_search *search,
- struct ast_data *data_root)
-{
- struct ast_data *data_hint;
- struct ast_hint *hint;
- int watchers;
- struct ao2_iterator i;
-
- if (ao2_container_count(hints) == 0) {
- return 0;
- }
-
- i = ao2_iterator_init(hints, 0);
- for (; (hint = ao2_iterator_next(&i)); ao2_ref(hint, -1)) {
- watchers = ao2_container_count(hint->callbacks);
- data_hint = ast_data_add_node(data_root, "hint");
- if (!data_hint) {
- continue;
- }
- ast_data_add_str(data_hint, "extension", ast_get_extension_name(hint->exten));
- ast_data_add_str(data_hint, "context", ast_get_context_name(ast_get_extension_context(hint->exten)));
- ast_data_add_str(data_hint, "application", ast_get_extension_app(hint->exten));
- ast_data_add_str(data_hint, "state", ast_extension_state2str(hint->laststate));
- ast_data_add_str(data_hint, "presence_state", ast_presence_state2str(hint->last_presence_state));
- ast_data_add_str(data_hint, "presence_subtype", S_OR(hint->last_presence_subtype, ""));
- ast_data_add_str(data_hint, "presence_subtype", S_OR(hint->last_presence_message, ""));
- ast_data_add_int(data_hint, "watchers", watchers);
-
- if (!ast_data_search_match(search, data_hint)) {
- ast_data_remove_node(data_root, data_hint);
- }
- }
- ao2_iterator_destroy(&i);
-
- return 0;
-}
-
-static const struct ast_data_handler hints_data_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = hints_data_provider_get
-};
-
-static const struct ast_data_entry pbx_data_providers[] = {
- AST_DATA_ENTRY("asterisk/core/hints", &hints_data_provider),
-};
-
static int action_extensionstatelist(struct mansession *s, const struct message *m)
{
const char *action_id = astman_get_header(m, "ActionID");
@@ -8431,7 +8381,6 @@ static void unload_pbx(void)
ast_cli_unregister_multiple(pbx_cli, ARRAY_LEN(pbx_cli));
ast_custom_function_unregister(&exception_function);
ast_custom_function_unregister(&testtime_function);
- ast_data_unregister(NULL);
}
int load_pbx(void)
@@ -8445,7 +8394,6 @@ int load_pbx(void)
ast_verb(2, "Registering builtin functions:\n");
ast_cli_register_multiple(pbx_cli, ARRAY_LEN(pbx_cli));
- ast_data_register_multiple_core(pbx_data_providers, ARRAY_LEN(pbx_data_providers));
__ast_custom_function_register(&exception_function, NULL);
__ast_custom_function_register(&testtime_function, NULL);
diff --git a/main/pbx_builtins.c b/main/pbx_builtins.c
index bc27b0d58..9d43c10ff 100644
--- a/main/pbx_builtins.c
+++ b/main/pbx_builtins.c
@@ -580,6 +580,42 @@
<para>This application waits for a specified number of <replaceable>seconds</replaceable>.</para>
</description>
</application>
+ <application name="WaitDigit" language="en_US">
+ <synopsis>
+ Waits for a digit to be entered.
+ </synopsis>
+ <syntax>
+ <parameter name="seconds">
+ <para>Can be passed with fractions of a second. For example, <literal>1.5</literal> will ask the
+ application to wait for 1.5 seconds.</para>
+ </parameter>
+ <parameter name="digits">
+ <para>Digits to accept, all others are ignored.</para>
+ </parameter>
+ </syntax>
+ <description>
+ <para>This application waits for the user to press one of the accepted
+ <replaceable>digits</replaceable> for a specified number of
+ <replaceable>seconds</replaceable>.</para>
+ <variablelist>
+ <variable name="WAITDIGITSTATUS">
+ <para>This is the final status of the command</para>
+ <value name="ERROR">Parameters are invalid.</value>
+ <value name="DTMF">An accepted digit was received.</value>
+ <value name="TIMEOUT">The timeout passed before any acceptable digits were received.</value>
+ <value name="CANCEL">The channel has hungup or was redirected.</value>
+ </variable>
+ <variable name="WAITDIGITRESULT">
+ <para>The digit that was received, only set if
+ <variable>WAITDIGITSTATUS</variable> is <literal>DTMF</literal>.</para>
+ </variable>
+ </variablelist>
+ </description>
+ <see-also>
+ <ref type="application">Wait</ref>
+ <ref type="application">WaitExten</ref>
+ </see-also>
+ </application>
<application name="WaitExten" language="en_US">
<synopsis>
Waits for an extension to be entered.
@@ -957,6 +993,47 @@ static int pbx_builtin_wait(struct ast_channel *chan, const char *data)
/*!
* \ingroup applications
*/
+static int pbx_builtin_waitdigit(struct ast_channel *chan, const char *data)
+{
+ int res;
+ int ms;
+ char *parse;
+ AST_DECLARE_APP_ARGS(args,
+ AST_APP_ARG(timeout);
+ AST_APP_ARG(digits);
+ );
+
+ parse = ast_strdupa(data);
+ AST_STANDARD_APP_ARGS(args, parse);
+
+ if (ast_app_parse_timelen(args.timeout, &ms, TIMELEN_SECONDS) || ms < 0) {
+ pbx_builtin_setvar_helper(chan, "WAITDIGITSTATUS", "ERROR");
+ return 0;
+ }
+
+ /* Wait for "n" seconds */
+ res = ast_waitfordigit_full(chan, ms, S_OR(args.digits, AST_DIGIT_ANY), -1, -1);
+ if (res < 0) {
+ pbx_builtin_setvar_helper(chan, "WAITDIGITSTATUS", "CANCEL");
+ return -1;
+ }
+
+ if (res == 0) {
+ pbx_builtin_setvar_helper(chan, "WAITDIGITSTATUS", "TIMEOUT");
+ } else {
+ char key[2];
+
+ snprintf(key, sizeof(key), "%c", res);
+ pbx_builtin_setvar_helper(chan, "WAITDIGITRESULT", key);
+ pbx_builtin_setvar_helper(chan, "WAITDIGITSTATUS", "DTMF");
+ }
+
+ return 0;
+}
+
+/*!
+ * \ingroup applications
+ */
static int pbx_builtin_waitexten(struct ast_channel *chan, const char *data)
{
int ms, res;
@@ -1410,6 +1487,7 @@ struct pbx_builtin {
{ "SayPhonetic", pbx_builtin_sayphonetic },
{ "SetAMAFlags", pbx_builtin_setamaflags },
{ "Wait", pbx_builtin_wait },
+ { "WaitDigit", pbx_builtin_waitdigit },
{ "WaitExten", pbx_builtin_waitexten }
};
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index 9cfae09f4..e078b2400 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -1495,21 +1495,24 @@ static int rtp_codecs_find_non_primary_dynamic_rx(struct ast_rtp_codecs *codecs)
* \param asterisk_format Non-zero if the given Asterisk format is present
* \param format Asterisk format to look for
* \param code The format to look for
+ * \param explicit Require the provided code to be explicitly used
*
* \note It is assumed that static_RTP_PT_lock is at least read locked before calling.
*
* \retval Numerical payload type
* \retval -1 if could not assign.
*/
-static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int asterisk_format, struct ast_format *format, int code)
+static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int asterisk_format, struct ast_format *format, int code, int explicit)
{
- int payload;
+ int payload = code;
struct ast_rtp_payload_type *new_type;
- payload = find_static_payload_type(asterisk_format, format, code);
+ if (!explicit) {
+ payload = find_static_payload_type(asterisk_format, format, code);
- if (payload < 0 && (!asterisk_format || ast_option_rtpusedynamic)) {
- return payload;
+ if (payload < 0 && (!asterisk_format || ast_option_rtpusedynamic)) {
+ return payload;
+ }
}
new_type = rtp_payload_type_alloc(format, payload, code, 1);
@@ -1525,9 +1528,9 @@ static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int
* The payload type is a static assignment
* or our default dynamic position is available.
*/
- rtp_codecs_payload_replace_rx(codecs, payload, new_type);
- } else if (-1 < (payload = find_unused_payload(codecs))
- || -1 < (payload = rtp_codecs_find_non_primary_dynamic_rx(codecs))) {
+ rtp_codecs_payload_replace_rx(codecs, payload, new_type);
+ } else if (!explicit && (-1 < (payload = find_unused_payload(codecs))
+ || -1 < (payload = rtp_codecs_find_non_primary_dynamic_rx(codecs)))) {
/*
* We found the first available empty dynamic position
* or we found a mapping that should no longer be
@@ -1535,6 +1538,11 @@ static int rtp_codecs_assign_payload_code_rx(struct ast_rtp_codecs *codecs, int
*/
new_type->payload = payload;
rtp_codecs_payload_replace_rx(codecs, payload, new_type);
+ } else if (explicit) {
+ /*
+ * They explicitly requested this payload number be used but it couldn't be
+ */
+ payload = -1;
} else {
/*
* There are no empty or non-primary dynamic positions
@@ -1595,13 +1603,18 @@ int ast_rtp_codecs_payload_code(struct ast_rtp_codecs *codecs, int asterisk_form
if (payload < 0) {
payload = rtp_codecs_assign_payload_code_rx(codecs, asterisk_format, format,
- code);
+ code, 0);
}
ast_rwlock_unlock(&static_RTP_PT_lock);
return payload;
}
+int ast_rtp_codecs_payload_set_rx(struct ast_rtp_codecs *codecs, int code, struct ast_format *format)
+{
+ return rtp_codecs_assign_payload_code_rx(codecs, 1, format, code, 1);
+}
+
int ast_rtp_codecs_payload_code_tx(struct ast_rtp_codecs *codecs, int asterisk_format, const struct ast_format *format, int code)
{
struct ast_rtp_payload_type *type;
@@ -2424,7 +2437,7 @@ int ast_rtp_instance_add_srtp_policy(struct ast_rtp_instance *instance, struct a
if (!*srtp) {
res = res_srtp->create(srtp, instance, remote_policy);
- } else {
+ } else if (remote_policy) {
res = res_srtp->replace(srtp, instance, remote_policy);
}
if (!res) {
@@ -3255,9 +3268,10 @@ int ast_rtp_engine_init(void)
set_next_mime_type(ast_format_siren7, 0, "audio", "G7221", 16000);
set_next_mime_type(ast_format_siren14, 0, "audio", "G7221", 32000);
set_next_mime_type(ast_format_g719, 0, "audio", "G719", 48000);
- /* Opus and VP8 */
+ /* Opus, VP8, and VP9 */
set_next_mime_type(ast_format_opus, 0, "audio", "opus", 48000);
set_next_mime_type(ast_format_vp8, 0, "video", "VP8", 90000);
+ set_next_mime_type(ast_format_vp9, 0, "video", "VP9", 90000);
/* Define the static rtp payload mappings */
add_static_payload(0, ast_format_ulaw, 0);
@@ -3298,6 +3312,7 @@ int ast_rtp_engine_init(void)
add_static_payload(105, ast_format_t140_red, 0); /* Real time text chat (with redundancy encoding) */
add_static_payload(106, ast_format_t140, 0); /* Real time text chat */
add_static_payload(107, ast_format_opus, 0);
+ add_static_payload(108, ast_format_vp9, 0);
add_static_payload(110, ast_format_speex, 0);
add_static_payload(111, ast_format_g726, 0);
@@ -3366,3 +3381,38 @@ const char *ast_rtp_instance_get_cname(struct ast_rtp_instance *rtp)
return cname;
}
+
+int ast_rtp_instance_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent)
+{
+ int res = -1;
+
+ if (parent && (child->engine != parent->engine)) {
+ return -1;
+ }
+
+ ao2_lock(child);
+ if (child->engine->bundle) {
+ res = child->engine->bundle(child, parent);
+ }
+ ao2_unlock(child);
+
+ return res;
+}
+
+void ast_rtp_instance_set_remote_ssrc(struct ast_rtp_instance *rtp, unsigned int ssrc)
+{
+ ao2_lock(rtp);
+ if (rtp->engine->set_remote_ssrc) {
+ rtp->engine->set_remote_ssrc(rtp, ssrc);
+ }
+ ao2_unlock(rtp);
+}
+
+void ast_rtp_instance_set_stream_num(struct ast_rtp_instance *rtp, int stream_num)
+{
+ ao2_lock(rtp);
+ if (rtp->engine->set_stream_num) {
+ rtp->engine->set_stream_num(rtp, stream_num);
+ }
+ ao2_unlock(rtp);
+}
diff --git a/main/say.c b/main/say.c
index 44f55e25a..c97dc9fd8 100644
--- a/main/say.c
+++ b/main/say.c
@@ -4441,9 +4441,9 @@ int ast_say_date_with_format_da(struct ast_channel *chan, time_t t, const char *
}
if (!res && next_item(&format[offset + 1]) == 'S') { /* minutes only if seconds follow */
if (tm.tm_min == 1) {
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
} else {
- res = wait_file(chan, ints, "digits/minutes", lang);
+ res = wait_file(chan, ints, "minutes", lang);
}
}
break;
@@ -4517,7 +4517,7 @@ int ast_say_date_with_format_da(struct ast_channel *chan, time_t t, const char *
if (!res) {
res = ast_say_number(chan, tm.tm_sec, ints, lang, "f");
if (!res) {
- res = wait_file(chan, ints, "digits/seconds", lang);
+ res = wait_file(chan, ints, "seconds", lang);
}
}
break;
@@ -4637,16 +4637,16 @@ int ast_say_date_with_format_de(struct ast_channel *chan, time_t t, const char *
case 'M':
/* Minute */
if (next_item(&format[offset + 1]) == 'S') { /* zero 'digits/0' only if seconds follow */
- res = ast_say_number(chan, tm.tm_min, ints, lang, "f"); /* female only if we say digits/minutes */
+ res = ast_say_number(chan, tm.tm_min, ints, lang, "f"); /* female only if we say minutes */
} else if (tm.tm_min > 0) {
res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL);
}
if (!res && next_item(&format[offset + 1]) == 'S') { /* minutes only if seconds follow */
if (tm.tm_min == 1) {
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
} else {
- res = wait_file(chan, ints, "digits/minutes", lang);
+ res = wait_file(chan, ints, "minutes", lang);
}
}
break;
@@ -4720,7 +4720,7 @@ int ast_say_date_with_format_de(struct ast_channel *chan, time_t t, const char *
if (!res) {
res = ast_say_number(chan, tm.tm_sec, ints, lang, "f");
if (!res) {
- res = wait_file(chan, ints, tm.tm_sec == 1 ? "digits/second" : "digits/seconds", lang);
+ res = wait_file(chan, ints, tm.tm_sec == 1 ? "second" : "seconds", lang);
}
}
break;
@@ -4853,9 +4853,9 @@ int ast_say_date_with_format_is(struct ast_channel *chan, time_t t, const char *
if (!res && next_item(&format[offset + 1]) == 'S') { /* minutes only if seconds follow */
/* Say minute/minutes depending on whether minutes end in 1 */
if ((tm.tm_min % 10 == 1) && (tm.tm_min != 11)) {
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
} else {
- res = wait_file(chan, ints, "digits/minutes", lang);
+ res = wait_file(chan, ints, "minutes", lang);
}
}
break;
@@ -4930,9 +4930,9 @@ int ast_say_date_with_format_is(struct ast_channel *chan, time_t t, const char *
res = ast_say_number(chan, tm.tm_sec, ints, lang, "f");
/* Say minute/minutes depending on whether seconds end in 1 */
if (!res && (tm.tm_sec % 10 == 1) && (tm.tm_sec != 11)) {
- res = wait_file(chan, ints, "digits/second", lang);
+ res = wait_file(chan, ints, "second", lang);
} else {
- res = wait_file(chan, ints, "digits/seconds", lang);
+ res = wait_file(chan, ints, "seconds", lang);
}
}
break;
@@ -5652,7 +5652,7 @@ int ast_say_date_with_format_fr(struct ast_channel *chan, time_t t, const char *
/* Seconds */
res = ast_say_number(chan, tm.tm_sec, ints, lang, (char * ) NULL);
if (!res) {
- res = wait_file(chan, ints, "digits/second", lang);
+ res = wait_file(chan, ints, "second", lang);
}
break;
case 'T':
@@ -6303,9 +6303,9 @@ int ast_say_date_with_format_pl(struct ast_channel *chan, time_t thetime, const
one = tm.tm_sec % 10;
if (one > 1 && one < 5 && ten != 1)
- res = wait_file(chan, ints, "digits/seconds", lang);
+ res = wait_file(chan, ints, "seconds", lang);
else
- res = wait_file(chan, ints, "digits/second", lang);
+ res = wait_file(chan, ints, "second", lang);
}
}
}
@@ -6469,9 +6469,9 @@ int ast_say_date_with_format_pt(struct ast_channel *chan, time_t t, const char *
res = ast_say_number(chan, tm.tm_min, ints, lang, NULL);
if (!res) {
if (tm.tm_min > 1) {
- res = wait_file(chan, ints, "digits/minutes", lang);
+ res = wait_file(chan, ints, "minutes", lang);
} else {
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
}
}
} else {
@@ -6567,9 +6567,9 @@ int ast_say_date_with_format_pt(struct ast_channel *chan, time_t t, const char *
res = ast_say_number(chan, tm.tm_sec, ints, lang, NULL);
if (!res) {
if (tm.tm_sec > 1) {
- res = wait_file(chan, ints, "digits/seconds", lang);
+ res = wait_file(chan, ints, "seconds", lang);
} else {
- res = wait_file(chan, ints, "digits/second", lang);
+ res = wait_file(chan, ints, "second", lang);
}
}
} else {
@@ -6783,7 +6783,7 @@ int ast_say_date_with_format_zh(struct ast_channel *chan, time_t t, const char *
}
}
if (!res) {
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
}
break;
case 'P':
@@ -6867,7 +6867,7 @@ int ast_say_date_with_format_zh(struct ast_channel *chan, time_t t, const char *
}
}
if (!res) {
- res = wait_file(chan, ints, "digits/second", lang);
+ res = wait_file(chan, ints, "second", lang);
}
break;
case 'T':
@@ -7022,7 +7022,7 @@ int ast_say_time_hu(struct ast_channel *chan, time_t t, const char *ints, const
if (tm.tm_min > 0) {
res = ast_say_number(chan, tm.tm_min, ints, lang, "f");
if (!res)
- res = ast_streamfile(chan, "digits/minute", lang);
+ res = ast_streamfile(chan, "minute", lang);
}
return res;
}
@@ -7117,9 +7117,9 @@ int ast_say_time_pt_BR(struct ast_channel *chan, time_t t, const char *ints, con
res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL);
if (!res) {
if (tm.tm_min > 1)
- res = wait_file(chan, ints, "digits/minutes", lang);
+ res = wait_file(chan, ints, "minutes", lang);
else
- res = wait_file(chan, ints, "digits/minute", lang);
+ res = wait_file(chan, ints, "minute", lang);
}
}
return res;
@@ -7179,7 +7179,7 @@ int ast_say_time_zh(struct ast_channel *chan, time_t t, const char *ints, const
if (!res)
res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL);
if (!res)
- res = ast_streamfile(chan, "digits/minute", lang);
+ res = ast_streamfile(chan, "minute", lang);
if (!res)
res = ast_waitstream(chan, ints);
return res;
@@ -7602,7 +7602,7 @@ int ast_say_datetime_zh(struct ast_channel *chan, time_t t, const char *ints, co
if (!res)
res = ast_say_number(chan, tm.tm_min, ints, lang, (char *) NULL);
if (!res)
- res = ast_streamfile(chan, "digits/minute", lang);
+ res = ast_streamfile(chan, "minute", lang);
if (!res)
res = ast_waitstream(chan, ints);
return res;
@@ -8480,7 +8480,7 @@ static int ast_say_date_with_format_gr(struct ast_channel *chan, time_t t, const
if (!res)
res = ast_say_number_full_gr(chan, tm.tm_sec, ints, lang, -1, -1);
if (!res)
- ast_copy_string(nextmsg, "digits/seconds", sizeof(nextmsg));
+ ast_copy_string(nextmsg, "seconds", sizeof(nextmsg));
res = wait_file(chan, ints, nextmsg, lang);
break;
case 'T':
diff --git a/main/sdp.c b/main/sdp.c
index bfb83e82f..fd10ba8c3 100644
--- a/main/sdp.c
+++ b/main/sdp.c
@@ -109,11 +109,9 @@ void ast_sdp_t_free(struct ast_sdp_t_line *t_line)
ast_free(t_line);
}
-void ast_sdp_free(struct ast_sdp *sdp)
+static void ast_sdp_dtor(void *vdoomed)
{
- if (!sdp) {
- return;
- }
+ struct ast_sdp *sdp = vdoomed;
ast_sdp_o_free(sdp->o_line);
ast_sdp_s_free(sdp->s_line);
@@ -121,7 +119,6 @@ void ast_sdp_free(struct ast_sdp *sdp)
ast_sdp_t_free(sdp->t_line);
ast_sdp_a_lines_free(sdp->a_lines);
ast_sdp_m_lines_free(sdp->m_lines);
- ast_free(sdp);
}
#define COPY_STR_AND_ADVANCE(p, dest, source) \
@@ -314,28 +311,28 @@ struct ast_sdp *ast_sdp_alloc(struct ast_sdp_o_line *o_line,
{
struct ast_sdp *new_sdp;
- new_sdp = ast_calloc(1, sizeof *new_sdp);
+ new_sdp = ao2_alloc_options(sizeof(*new_sdp), ast_sdp_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!new_sdp) {
return NULL;
}
new_sdp->a_lines = ast_calloc(1, sizeof(*new_sdp->a_lines));
if (!new_sdp->a_lines) {
- ast_sdp_free(new_sdp);
+ ao2_ref(new_sdp, -1);
return NULL;
}
if (AST_VECTOR_INIT(new_sdp->a_lines, 20)) {
- ast_sdp_free(new_sdp);
+ ao2_ref(new_sdp, -1);
return NULL;
}
new_sdp->m_lines = ast_calloc(1, sizeof(*new_sdp->m_lines));
if (!new_sdp->m_lines) {
- ast_sdp_free(new_sdp);
+ ao2_ref(new_sdp, -1);
return NULL;
}
if (AST_VECTOR_INIT(new_sdp->m_lines, 20)) {
- ast_sdp_free(new_sdp);
+ ao2_ref(new_sdp, -1);
return NULL;
}
@@ -741,6 +738,62 @@ static void process_fmtp_lines(const struct ast_sdp_m_line *m_line, int payload,
}
}
+/*!
+ * \internal
+ * \brief Determine the RTP stream direction in the given a lines.
+ * \since 15.0.0
+ *
+ * \param a_lines Attribute lines to search.
+ *
+ * \retval Stream direction on success.
+ * \retval AST_STREAM_STATE_REMOVED on failure.
+ *
+ * \return Nothing
+ */
+static enum ast_stream_state get_a_line_direction(const struct ast_sdp_a_lines *a_lines)
+{
+ if (a_lines) {
+ enum ast_stream_state direction;
+ int idx;
+ const struct ast_sdp_a_line *a_line;
+
+ for (idx = 0; idx < AST_VECTOR_SIZE(a_lines); ++idx) {
+ a_line = AST_VECTOR_GET(a_lines, idx);
+ direction = ast_stream_str2state(a_line->name);
+ if (direction != AST_STREAM_STATE_REMOVED) {
+ return direction;
+ }
+ }
+ }
+
+ return AST_STREAM_STATE_REMOVED;
+}
+
+/*!
+ * \internal
+ * \brief Determine the RTP stream direction.
+ * \since 15.0.0
+ *
+ * \param a_lines The SDP media global attributes
+ * \param m_line The SDP media section to convert
+ *
+ * \return Stream direction
+ */
+static enum ast_stream_state get_stream_direction(const struct ast_sdp_a_lines *a_lines, const struct ast_sdp_m_line *m_line)
+{
+ enum ast_stream_state direction;
+
+ direction = get_a_line_direction(m_line->a_lines);
+ if (direction != AST_STREAM_STATE_REMOVED) {
+ return direction;
+ }
+ direction = get_a_line_direction(a_lines);
+ if (direction != AST_STREAM_STATE_REMOVED) {
+ return direction;
+ }
+ return AST_STREAM_STATE_SENDRECV;
+}
+
/*
* Needed so we don't have an external function referenced as data.
* The dynamic linker doesn't handle that very well.
@@ -758,13 +811,14 @@ static void rtp_codecs_free(struct ast_rtp_codecs *codecs)
* Given an m-line from an SDP, convert it into an ast_stream structure.
* This takes formats, as well as clock-rate and fmtp attributes into account.
*
+ * \param a_lines The SDP media global attributes
* \param m_line The SDP media section to convert
* \param g726_non_standard Non-zero if G.726 is non-standard
*
* \retval NULL An error occurred
* \retval non-NULL The converted stream
*/
-static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line, int g726_non_standard)
+static struct ast_stream *get_stream_from_m(const struct ast_sdp_a_lines *a_lines, const struct ast_sdp_m_line *m_line, int g726_non_standard)
{
int i;
int non_ast_fmts;
@@ -793,6 +847,14 @@ static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line,
ao2_ref(caps, -1);
return NULL;
}
+ ast_stream_set_data(stream, AST_STREAM_DATA_RTP_CODECS, codecs,
+ (ast_stream_data_free_fn) rtp_codecs_free);
+
+ if (!m_line->port) {
+ /* Stream is declined. There may not be any attributes. */
+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+ break;
+ }
options = g726_non_standard ? AST_RTP_OPT_G726_NONSTANDARD : 0;
for (i = 0; i < ast_sdp_m_get_payload_count(m_line); ++i) {
@@ -819,10 +881,16 @@ static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line,
}
ast_rtp_codecs_payload_formats(codecs, caps, &non_ast_fmts);
- ast_stream_set_data(stream, AST_STREAM_DATA_RTP_CODECS, codecs,
- (ast_stream_data_free_fn) rtp_codecs_free);
+
+ ast_stream_set_state(stream, get_stream_direction(a_lines, m_line));
break;
case AST_MEDIA_TYPE_IMAGE:
+ if (!m_line->port) {
+ /* Stream is declined. There may not be any attributes. */
+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+ break;
+ }
+
for (i = 0; i < ast_sdp_m_get_payload_count(m_line); ++i) {
struct ast_sdp_payload *payload;
@@ -830,12 +898,15 @@ static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line,
payload = ast_sdp_m_get_payload(m_line, i);
if (!strcasecmp(payload->fmt, "t38")) {
ast_format_cap_append(caps, ast_format_t38, 0);
+ ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV);
}
}
break;
case AST_MEDIA_TYPE_UNKNOWN:
case AST_MEDIA_TYPE_TEXT:
case AST_MEDIA_TYPE_END:
+ /* Consider these unsupported streams as declined */
+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
break;
}
@@ -858,7 +929,7 @@ struct ast_stream_topology *ast_get_topology_from_sdp(const struct ast_sdp *sdp,
for (i = 0; i < ast_sdp_get_m_count(sdp); ++i) {
struct ast_stream *stream;
- stream = get_stream_from_m(ast_sdp_get_m(sdp, i), g726_non_standard);
+ stream = get_stream_from_m(sdp->a_lines, ast_sdp_get_m(sdp, i), g726_non_standard);
if (!stream) {
/*
* The topology cannot match the SDP because
diff --git a/main/sdp_options.c b/main/sdp_options.c
index ab8fb2973..79efbafa0 100644
--- a/main/sdp_options.c
+++ b/main/sdp_options.c
@@ -27,6 +27,7 @@
#define DEFAULT_ICE AST_SDP_ICE_DISABLED
#define DEFAULT_IMPL AST_SDP_IMPL_STRING
#define DEFAULT_ENCRYPTION AST_SDP_ENCRYPTION_DISABLED
+#define DEFAULT_MAX_STREAMS 16 /* Set to match our PJPROJECT PJMEDIA_MAX_SDP_MEDIA. */
#define DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(field, assert_on_null) \
void ast_sdp_options_set_##field(struct ast_sdp_options *options, const char *value) \
@@ -60,6 +61,12 @@ DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(sdpowner, 0);
DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(sdpsession, 0);
DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(rtp_engine, 0);
+DEFINE_GETTERS_SETTERS_FOR(void *, state_context);
+DEFINE_GETTERS_SETTERS_FOR(ast_sdp_answerer_modify_cb, answerer_modify_cb);
+DEFINE_GETTERS_SETTERS_FOR(ast_sdp_offerer_modify_cb, offerer_modify_cb);
+DEFINE_GETTERS_SETTERS_FOR(ast_sdp_offerer_config_cb, offerer_config_cb);
+DEFINE_GETTERS_SETTERS_FOR(ast_sdp_preapply_cb, preapply_cb);
+DEFINE_GETTERS_SETTERS_FOR(ast_sdp_postapply_cb, postapply_cb);
DEFINE_GETTERS_SETTERS_FOR(unsigned int, rtp_symmetric);
DEFINE_GETTERS_SETTERS_FOR(unsigned int, udptl_symmetric);
DEFINE_GETTERS_SETTERS_FOR(enum ast_t38_ec_modes, udptl_error_correction);
@@ -71,18 +78,127 @@ DEFINE_GETTERS_SETTERS_FOR(unsigned int, tos_audio);
DEFINE_GETTERS_SETTERS_FOR(unsigned int, cos_audio);
DEFINE_GETTERS_SETTERS_FOR(unsigned int, tos_video);
DEFINE_GETTERS_SETTERS_FOR(unsigned int, cos_video);
+DEFINE_GETTERS_SETTERS_FOR(unsigned int, max_streams);
DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_dtmf, dtmf);
DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_ice, ice);
DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_impl, impl);
DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_encryption, encryption);
DEFINE_GETTERS_SETTERS_FOR(unsigned int, ssrc);
+struct ast_sched_context *ast_sdp_options_get_sched_type(const struct ast_sdp_options *options, enum ast_media_type type)
+{
+ struct ast_sched_context *sched = NULL;
+
+ switch (type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ case AST_MEDIA_TYPE_IMAGE:
+ case AST_MEDIA_TYPE_TEXT:
+ sched = options->sched[type];
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_END:
+ break;
+ }
+ return sched;
+}
+
+void ast_sdp_options_set_sched_type(struct ast_sdp_options *options, enum ast_media_type type, struct ast_sched_context *sched)
+{
+ switch (type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ case AST_MEDIA_TYPE_IMAGE:
+ case AST_MEDIA_TYPE_TEXT:
+ options->sched[type] = sched;
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_END:
+ break;
+ }
+}
+
+struct ast_format_cap *ast_sdp_options_get_format_cap_type(const struct ast_sdp_options *options,
+ enum ast_media_type type)
+{
+ struct ast_format_cap *cap = NULL;
+
+ switch (type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ case AST_MEDIA_TYPE_IMAGE:
+ case AST_MEDIA_TYPE_TEXT:
+ cap = options->caps[type];
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_END:
+ break;
+ }
+ return cap;
+}
+
+void ast_sdp_options_set_format_cap_type(struct ast_sdp_options *options,
+ enum ast_media_type type, struct ast_format_cap *cap)
+{
+ switch (type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ case AST_MEDIA_TYPE_IMAGE:
+ case AST_MEDIA_TYPE_TEXT:
+ ao2_cleanup(options->caps[type]);
+ options->caps[type] = NULL;
+ if (cap && !ast_format_cap_empty(cap)) {
+ ao2_ref(cap, +1);
+ options->caps[type] = cap;
+ }
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_END:
+ break;
+ }
+}
+
+void ast_sdp_options_set_format_caps(struct ast_sdp_options *options,
+ struct ast_format_cap *cap)
+{
+ enum ast_media_type type;
+
+ for (type = AST_MEDIA_TYPE_UNKNOWN; type < AST_MEDIA_TYPE_END; ++type) {
+ ao2_cleanup(options->caps[type]);
+ options->caps[type] = NULL;
+ }
+
+ if (!cap || ast_format_cap_empty(cap)) {
+ return;
+ }
+
+ for (type = AST_MEDIA_TYPE_UNKNOWN + 1; type < AST_MEDIA_TYPE_END; ++type) {
+ struct ast_format_cap *type_cap;
+
+ type_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!type_cap) {
+ continue;
+ }
+
+ ast_format_cap_set_framing(type_cap, ast_format_cap_get_framing(cap));
+ if (ast_format_cap_append_from_cap(type_cap, cap, type)
+ || ast_format_cap_empty(type_cap)) {
+ ao2_ref(type_cap, -1);
+ continue;
+ }
+
+ /* This takes the allocation reference */
+ options->caps[type] = type_cap;
+ }
+}
+
static void set_defaults(struct ast_sdp_options *options)
{
options->dtmf = DEFAULT_DTMF;
options->ice = DEFAULT_ICE;
options->impl = DEFAULT_IMPL;
options->encryption = DEFAULT_ENCRYPTION;
+ options->max_streams = DEFAULT_MAX_STREAMS;
}
struct ast_sdp_options *ast_sdp_options_alloc(void)
@@ -105,6 +221,15 @@ struct ast_sdp_options *ast_sdp_options_alloc(void)
void ast_sdp_options_free(struct ast_sdp_options *options)
{
+ enum ast_media_type type;
+
+ if (!options) {
+ return;
+ }
+
+ for (type = AST_MEDIA_TYPE_UNKNOWN; type < AST_MEDIA_TYPE_END; ++type) {
+ ao2_cleanup(options->caps[type]);
+ }
ast_string_field_free_memory(options);
ast_free(options);
}
diff --git a/main/sdp_private.h b/main/sdp_private.h
index a0b63df03..48bedc802 100644
--- a/main/sdp_private.h
+++ b/main/sdp_private.h
@@ -24,7 +24,7 @@
struct ast_sdp_options {
AST_DECLARE_STRING_FIELDS(
- /*! Media address to use in SDP */
+ /*! Media address to advertise in SDP session c= line */
AST_STRING_FIELD(media_address);
/*! Optional address of the interface media should use. */
AST_STRING_FIELD(interface_address);
@@ -35,12 +35,27 @@ struct ast_sdp_options {
/*! RTP Engine Name */
AST_STRING_FIELD(rtp_engine);
);
+ /*! Scheduler context for the media stream types (Mainly for RTP) */
+ struct ast_sched_context *sched[AST_MEDIA_TYPE_END];
+ /*! Capabilities to create new streams of the indexed media type. */
+ struct ast_format_cap *caps[AST_MEDIA_TYPE_END];
+ /*! User supplied context data pointer for the SDP state. */
+ void *state_context;
+ /*! Modify negotiated topology before create answer SDP callback. */
+ ast_sdp_answerer_modify_cb answerer_modify_cb;
+ /*! Modify proposed topology before create offer SDP callback. */
+ ast_sdp_offerer_modify_cb offerer_modify_cb;
+ /*! Configure proposed topology extra stream options before create offer SDP callback. */
+ ast_sdp_offerer_config_cb offerer_config_cb;
+ /*! Negotiated topology is about to be applied callback. */
+ ast_sdp_preapply_cb preapply_cb;
+ /*! Negotiated topology was just applied callback. */
+ ast_sdp_postapply_cb postapply_cb;
struct {
unsigned int rtp_symmetric:1;
unsigned int udptl_symmetric:1;
unsigned int rtp_ipv6:1;
unsigned int g726_non_standard:1;
- unsigned int locally_held:1;
unsigned int rtcp_mux:1;
unsigned int ssrc:1;
};
@@ -50,6 +65,8 @@ struct ast_sdp_options {
unsigned int tos_video;
unsigned int cos_video;
unsigned int udptl_far_max_datagram;
+ /*! Maximum number of streams to allow. */
+ unsigned int max_streams;
};
enum ast_sdp_options_dtmf dtmf;
enum ast_sdp_options_ice ice;
diff --git a/main/sdp_state.c b/main/sdp_state.c
index 0f06bf9e3..a77d96da5 100644
--- a/main/sdp_state.c
+++ b/main/sdp_state.c
@@ -85,12 +85,39 @@ struct sdp_state_stream {
};
/*! An explicit connection address for this stream */
struct ast_sockaddr connection_address;
- /*! Whether this stream is held or not */
- unsigned int locally_held;
+ /*!
+ * \brief Stream is on hold by remote side
+ *
+ * \note This flag is never set on the
+ * sdp_state->proposed_capabilities->streams states. This is useful
+ * when the remote sends us a reINVITE with a deferred SDP to place
+ * us on and off of hold.
+ */
+ unsigned int remotely_held:1;
+ /*! Stream is on hold by local side */
+ unsigned int locally_held:1;
/*! UDPTL session parameters */
struct ast_control_t38_parameters t38_local_params;
};
+static int sdp_is_stream_type_supported(enum ast_media_type type)
+{
+ int is_supported = 0;
+
+ switch (type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ case AST_MEDIA_TYPE_IMAGE:
+ is_supported = 1;
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_TEXT:
+ case AST_MEDIA_TYPE_END:
+ break;
+ }
+ return is_supported;
+}
+
static void sdp_state_rtp_destroy(void *obj)
{
struct sdp_state_rtp *rtp = obj;
@@ -135,8 +162,6 @@ struct sdp_state_capabilities {
struct ast_stream_topology *topology;
/*! Additional information about the streams */
struct sdp_state_streams streams;
- /*! An explicit global connection address */
- struct ast_sockaddr connection_address;
};
static void sdp_state_capabilities_free(struct sdp_state_capabilities *capabilities)
@@ -151,12 +176,6 @@ static void sdp_state_capabilities_free(struct sdp_state_capabilities *capabilit
ast_free(capabilities);
}
-/* TODO
- * This isn't set anywhere yet.
- */
-/*! \brief Scheduler for RTCP purposes */
-static struct ast_sched_context *sched;
-
/*! \brief Internal function which creates an RTP instance */
static struct sdp_state_rtp *create_rtp(const struct ast_sdp_options *options,
enum ast_media_type media_type)
@@ -185,7 +204,8 @@ static struct sdp_state_rtp *create_rtp(const struct ast_sdp_options *options,
return NULL;
}
- rtp->instance = ast_rtp_instance_new(options->rtp_engine, sched, media_address, NULL);
+ rtp->instance = ast_rtp_instance_new(options->rtp_engine,
+ ast_sdp_options_get_sched_type(options, media_type), media_address, NULL);
if (!rtp->instance) {
ast_log(LOG_ERROR, "Unable to create RTP instance using RTP engine '%s'\n",
options->rtp_engine);
@@ -274,30 +294,80 @@ static struct sdp_state_udptl *create_udptl(const struct ast_sdp_options *option
return udptl;
}
+static struct ast_stream *merge_local_stream(const struct ast_sdp_options *options,
+ const struct ast_stream *update);
+
static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const struct ast_stream_topology *topology,
const struct ast_sdp_options *options)
{
struct sdp_state_capabilities *capabilities;
- int i;
+ struct ast_stream *stream;
+ unsigned int topology_count;
+ unsigned int max_streams;
+ unsigned int idx;
capabilities = ast_calloc(1, sizeof(*capabilities));
if (!capabilities) {
return NULL;
}
- capabilities->topology = ast_stream_topology_clone(topology);
+ capabilities->topology = ast_stream_topology_alloc();
if (!capabilities->topology) {
sdp_state_capabilities_free(capabilities);
return NULL;
}
- if (AST_VECTOR_INIT(&capabilities->streams, ast_stream_topology_get_count(topology))) {
+ max_streams = ast_sdp_options_get_max_streams(options);
+ if (topology) {
+ topology_count = ast_stream_topology_get_count(topology);
+ } else {
+ topology_count = 0;
+ }
+
+ /* Gather acceptable streams from the initial topology */
+ for (idx = 0; idx < topology_count; ++idx) {
+ stream = ast_stream_topology_get_stream(topology, idx);
+ if (!sdp_is_stream_type_supported(ast_stream_get_type(stream))) {
+ /* Delete the unsupported stream from the initial topology */
+ continue;
+ }
+ if (max_streams <= ast_stream_topology_get_count(capabilities->topology)) {
+ /* Cannot support any more streams */
+ break;
+ }
+
+ stream = merge_local_stream(options, stream);
+ if (!stream) {
+ sdp_state_capabilities_free(capabilities);
+ return NULL;
+ }
+
+ if (ast_stream_topology_append_stream(capabilities->topology, stream) < 0) {
+ ast_stream_free(stream);
+ sdp_state_capabilities_free(capabilities);
+ return NULL;
+ }
+ }
+
+ /*
+ * Remove trailing declined streams from the initial built topology.
+ * No need to waste space in the SDP with these unused slots.
+ */
+ for (idx = ast_stream_topology_get_count(capabilities->topology); idx--;) {
+ stream = ast_stream_topology_get_stream(capabilities->topology, idx);
+ if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) {
+ break;
+ }
+ ast_stream_topology_del_stream(capabilities->topology, idx);
+ }
+
+ topology_count = ast_stream_topology_get_count(capabilities->topology);
+ if (AST_VECTOR_INIT(&capabilities->streams, topology_count)) {
sdp_state_capabilities_free(capabilities);
return NULL;
}
- ast_sockaddr_setnull(&capabilities->connection_address);
- for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {
+ for (idx = 0; idx < topology_count; ++idx) {
struct sdp_state_stream *state_stream;
state_stream = ast_calloc(1, sizeof(*state_stream));
@@ -306,32 +376,34 @@ static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const st
return NULL;
}
- state_stream->type = ast_stream_get_type(ast_stream_topology_get_stream(topology, i));
- switch (state_stream->type) {
- case AST_MEDIA_TYPE_AUDIO:
- case AST_MEDIA_TYPE_VIDEO:
- state_stream->rtp = create_rtp(options, state_stream->type);
- if (!state_stream->rtp) {
- sdp_state_stream_free(state_stream);
- sdp_state_capabilities_free(capabilities);
- return NULL;
- }
- break;
- case AST_MEDIA_TYPE_IMAGE:
- state_stream->udptl = create_udptl(options);
- if (!state_stream->udptl) {
- sdp_state_stream_free(state_stream);
- sdp_state_capabilities_free(capabilities);
- return NULL;
+ stream = ast_stream_topology_get_stream(capabilities->topology, idx);
+ state_stream->type = ast_stream_get_type(stream);
+ if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) {
+ switch (state_stream->type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ state_stream->rtp = create_rtp(options, state_stream->type);
+ if (!state_stream->rtp) {
+ sdp_state_stream_free(state_stream);
+ sdp_state_capabilities_free(capabilities);
+ return NULL;
+ }
+ break;
+ case AST_MEDIA_TYPE_IMAGE:
+ state_stream->udptl = create_udptl(options);
+ if (!state_stream->udptl) {
+ sdp_state_stream_free(state_stream);
+ sdp_state_capabilities_free(capabilities);
+ return NULL;
+ }
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_TEXT:
+ case AST_MEDIA_TYPE_END:
+ /* Unsupported stream type already handled earlier */
+ ast_assert(0);
+ break;
}
- break;
- case AST_MEDIA_TYPE_UNKNOWN:
- case AST_MEDIA_TYPE_TEXT:
- case AST_MEDIA_TYPE_END:
- ast_assert(0);
- sdp_state_stream_free(state_stream);
- sdp_state_capabilities_free(capabilities);
- return NULL;
}
if (AST_VECTOR_APPEND(&capabilities->streams, state_stream)) {
@@ -355,12 +427,12 @@ static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const st
* is allocated, this topology is used to create the proposed_capabilities.
*
* If we are the SDP offerer, then the proposed_capabilities are what are used
- * to generate the SDP offer. When the SDP answer arrives, the proposed capabilities
- * are merged with the SDP answer to create the negotiated capabilities.
+ * to generate the offer SDP. When the answer SDP arrives, the proposed capabilities
+ * are merged with the answer SDP to create the negotiated capabilities.
*
- * If we are the SDP answerer, then the incoming SDP offer is merged with our
+ * If we are the SDP answerer, then the incoming offer SDP is merged with our
* proposed capabilities to to create the negotiated capabilities. These negotiated
- * capabilities are what we send in our SDP answer.
+ * capabilities are what we send in our answer SDP.
*
* Any changes that a user of the API performs will occur on the proposed capabilities.
* The negotiated capabilities are only altered based on actual SDP negotiation. This is
@@ -372,17 +444,33 @@ struct ast_sdp_state {
struct sdp_state_capabilities *negotiated_capabilities;
/*! Proposed capabilities */
struct sdp_state_capabilities *proposed_capabilities;
- /*! Local SDP. Generated via the options and currently negotiated/proposed capabilities. */
+ /*!
+ * \brief New topology waiting to be merged.
+ *
+ * \details
+ * Repeated topology updates are merged into each other here until
+ * negotiations are restarted and we create an offer.
+ */
+ struct ast_stream_topology *pending_topology_update;
+ /*! Local SDP. Generated via the options and negotiated/proposed capabilities. */
struct ast_sdp *local_sdp;
+ /*! Saved remote SDP */
+ struct ast_sdp *remote_sdp;
/*! SDP options. Configured options beyond media capabilities. */
struct ast_sdp_options *options;
/*! Translator that puts SDPs into the expected representation */
struct ast_sdp_translator *translator;
+ /*! An explicit global connection address */
+ struct ast_sockaddr connection_address;
/*! The role that we occupy in SDP negotiation */
enum ast_sdp_role role;
+ /*! TRUE if all streams on hold by local side */
+ unsigned int locally_held:1;
+ /*! TRUE if the remote offer resulted in all streams being declined. */
+ unsigned int remote_offer_rejected:1;
};
-struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams,
+struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *topology,
struct ast_sdp_options *options)
{
struct ast_sdp_state *sdp_state;
@@ -400,7 +488,7 @@ struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams,
return NULL;
}
- sdp_state->proposed_capabilities = sdp_initialize_state_capabilities(streams, options);
+ sdp_state->proposed_capabilities = sdp_initialize_state_capabilities(topology, options);
if (!sdp_state->proposed_capabilities) {
ast_sdp_state_free(sdp_state);
return NULL;
@@ -419,12 +507,217 @@ void ast_sdp_state_free(struct ast_sdp_state *sdp_state)
sdp_state_capabilities_free(sdp_state->negotiated_capabilities);
sdp_state_capabilities_free(sdp_state->proposed_capabilities);
- ast_sdp_free(sdp_state->local_sdp);
+ ao2_cleanup(sdp_state->local_sdp);
+ ao2_cleanup(sdp_state->remote_sdp);
ast_sdp_options_free(sdp_state->options);
ast_sdp_translator_free(sdp_state->translator);
ast_free(sdp_state);
}
+/*!
+ * \internal
+ * \brief Allow a configured callback to alter the new negotiated joint topology.
+ * \since 15.0.0
+ *
+ * \details
+ * The callback can alter topology stream names, formats, or decline streams.
+ *
+ * \param sdp_state
+ * \param topology Joint topology that we intend to generate the answer SDP.
+ *
+ * \return Nothing
+ */
+static void sdp_state_cb_answerer_modify_topology(const struct ast_sdp_state *sdp_state,
+ struct ast_stream_topology *topology)
+{
+ ast_sdp_answerer_modify_cb cb;
+
+ cb = ast_sdp_options_get_answerer_modify_cb(sdp_state->options);
+ if (cb) {
+ void *context;
+ const struct ast_stream_topology *neg_topology;/*!< Last negotiated topology */
+#ifdef AST_DEVMODE
+ struct ast_stream *stream;
+ int idx;
+ enum ast_media_type type[ast_stream_topology_get_count(topology)];
+ enum ast_stream_state state[ast_stream_topology_get_count(topology)];
+
+ /*
+ * Save stream types and states to validate that they don't
+ * get changed unexpectedly.
+ */
+ for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) {
+ stream = ast_stream_topology_get_stream(topology, idx);
+ type[idx] = ast_stream_get_type(stream);
+ state[idx] = ast_stream_get_state(stream);
+ }
+#endif
+
+ context = ast_sdp_options_get_state_context(sdp_state->options);
+ neg_topology = sdp_state->negotiated_capabilities
+ ? sdp_state->negotiated_capabilities->topology : NULL;
+ cb(context, neg_topology, topology);
+
+#ifdef AST_DEVMODE
+ for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) {
+ stream = ast_stream_topology_get_stream(topology, idx);
+
+ /* Check that active streams have at least one format */
+ ast_assert(ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED
+ || (ast_stream_get_formats(stream)
+ && ast_format_cap_count(ast_stream_get_formats(stream))));
+
+ /* Check that stream types didn't change. */
+ ast_assert(type[idx] == ast_stream_get_type(stream));
+
+ /* Check that streams didn't get resurected. */
+ ast_assert(state[idx] != AST_STREAM_STATE_REMOVED
+ || ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED);
+ }
+#endif
+ }
+}
+
+/*!
+ * \internal
+ * \brief Allow a configured callback to alter the merged local topology.
+ * \since 15.0.0
+ *
+ * \details
+ * The callback can modify streams in the merged topology. The
+ * callback can decline, add/remove/update formats, or rename
+ * streams. Changing anything else on the streams is likely to not
+ * end well.
+ *
+ * \param sdp_state
+ * \param topology Merged topology that we intend to generate the offer SDP.
+ *
+ * \return Nothing
+ */
+static void sdp_state_cb_offerer_modify_topology(const struct ast_sdp_state *sdp_state,
+ struct ast_stream_topology *topology)
+{
+ ast_sdp_offerer_modify_cb cb;
+
+ cb = ast_sdp_options_get_offerer_modify_cb(sdp_state->options);
+ if (cb) {
+ void *context;
+ const struct ast_stream_topology *neg_topology;/*!< Last negotiated topology */
+
+ context = ast_sdp_options_get_state_context(sdp_state->options);
+ neg_topology = sdp_state->negotiated_capabilities
+ ? sdp_state->negotiated_capabilities->topology : NULL;
+ cb(context, neg_topology, topology);
+
+#ifdef AST_DEVMODE
+ {
+ struct ast_stream *stream;
+ int idx;
+
+ /* Check that active streams have at least one format */
+ for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) {
+ stream = ast_stream_topology_get_stream(topology, idx);
+ ast_assert(ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED
+ || (ast_stream_get_formats(stream)
+ && ast_format_cap_count(ast_stream_get_formats(stream))));
+ }
+ }
+#endif
+ }
+}
+
+/*!
+ * \internal
+ * \brief Allow a configured callback to configure the merged local topology.
+ * \since 15.0.0
+ *
+ * \details
+ * The callback can configure other parameters associated with each
+ * active stream on the topology. The callback can call several SDP
+ * API calls to configure the proposed capabilities of the streams
+ * before we create the offer SDP. For example, the callback could
+ * configure a stream specific connection address, T.38 parameters,
+ * RTP instance, or UDPTL instance parameters.
+ *
+ * \param sdp_state
+ * \param topology Merged topology that we intend to generate the offer SDP.
+ *
+ * \return Nothing
+ */
+static void sdp_state_cb_offerer_config_topology(const struct ast_sdp_state *sdp_state,
+ const struct ast_stream_topology *topology)
+{
+ ast_sdp_offerer_config_cb cb;
+
+ cb = ast_sdp_options_get_offerer_config_cb(sdp_state->options);
+ if (cb) {
+ void *context;
+
+ context = ast_sdp_options_get_state_context(sdp_state->options);
+ cb(context, topology);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Call any registered pre-apply topology callback.
+ * \since 15.0.0
+ *
+ * \param sdp_state
+ * \param topology
+ *
+ * \return Nothing
+ */
+static void sdp_state_cb_preapply_topology(const struct ast_sdp_state *sdp_state,
+ const struct ast_stream_topology *topology)
+{
+ ast_sdp_preapply_cb cb;
+
+ cb = ast_sdp_options_get_preapply_cb(sdp_state->options);
+ if (cb) {
+ void *context;
+
+ context = ast_sdp_options_get_state_context(sdp_state->options);
+ cb(context, topology);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Call any registered post-apply topology callback.
+ * \since 15.0.0
+ *
+ * \param sdp_state
+ * \param topology
+ *
+ * \return Nothing
+ */
+static void sdp_state_cb_postapply_topology(const struct ast_sdp_state *sdp_state,
+ const struct ast_stream_topology *topology)
+{
+ ast_sdp_postapply_cb cb;
+
+ cb = ast_sdp_options_get_postapply_cb(sdp_state->options);
+ if (cb) {
+ void *context;
+
+ context = ast_sdp_options_get_state_context(sdp_state->options);
+ cb(context, topology);
+ }
+}
+
+static const struct sdp_state_capabilities *sdp_state_get_joint_capabilities(
+ const struct ast_sdp_state *sdp_state)
+{
+ ast_assert(sdp_state != NULL);
+
+ if (sdp_state->negotiated_capabilities) {
+ return sdp_state->negotiated_capabilities;
+ }
+
+ return sdp_state->proposed_capabilities;
+}
+
static struct sdp_state_stream *sdp_state_get_stream(const struct ast_sdp_state *sdp_state, int stream_index)
{
if (stream_index >= AST_VECTOR_SIZE(&sdp_state->proposed_capabilities->streams)) {
@@ -434,6 +727,18 @@ static struct sdp_state_stream *sdp_state_get_stream(const struct ast_sdp_state
return AST_VECTOR_GET(&sdp_state->proposed_capabilities->streams, stream_index);
}
+static struct sdp_state_stream *sdp_state_get_joint_stream(const struct ast_sdp_state *sdp_state, int stream_index)
+{
+ const struct sdp_state_capabilities *capabilities;
+
+ capabilities = sdp_state_get_joint_capabilities(sdp_state);
+ if (AST_VECTOR_SIZE(&capabilities->streams) <= stream_index) {
+ return NULL;
+ }
+
+ return AST_VECTOR_GET(&capabilities->streams, stream_index);
+}
+
struct ast_rtp_instance *ast_sdp_state_get_rtp_instance(
const struct ast_sdp_state *sdp_state, int stream_index)
{
@@ -473,35 +778,34 @@ const struct ast_sockaddr *ast_sdp_state_get_connection_address(const struct ast
{
ast_assert(sdp_state != NULL);
- return &sdp_state->proposed_capabilities->connection_address;
+ return &sdp_state->connection_address;
}
-int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_state,
- int stream_index, struct ast_sockaddr *address)
+static int sdp_state_stream_get_connection_address(const struct ast_sdp_state *sdp_state,
+ struct sdp_state_stream *stream_state, struct ast_sockaddr *address)
{
- struct sdp_state_stream *stream_state;
-
ast_assert(sdp_state != NULL);
+ ast_assert(stream_state != NULL);
ast_assert(address != NULL);
- stream_state = sdp_state_get_stream(sdp_state, stream_index);
- if (!stream_state) {
- return -1;
- }
-
/* If an explicit connection address has been provided for the stream return it */
if (!ast_sockaddr_isnull(&stream_state->connection_address)) {
ast_sockaddr_copy(address, &stream_state->connection_address);
return 0;
}
- switch (ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->proposed_capabilities->topology,
- stream_index))) {
+ switch (stream_state->type) {
case AST_MEDIA_TYPE_AUDIO:
case AST_MEDIA_TYPE_VIDEO:
+ if (!stream_state->rtp->instance) {
+ return -1;
+ }
ast_rtp_instance_get_local_address(stream_state->rtp->instance, address);
break;
case AST_MEDIA_TYPE_IMAGE:
+ if (!stream_state->udptl->instance) {
+ return -1;
+ }
ast_udptl_get_us(stream_state->udptl->instance, address);
break;
case AST_MEDIA_TYPE_UNKNOWN:
@@ -510,27 +814,45 @@ int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_
return -1;
}
+ if (ast_sockaddr_isnull(address)) {
+ /* No address is set on the stream state. */
+ return -1;
+ }
+
/* If an explicit global connection address is set use it here for the IP part */
- if (!ast_sockaddr_isnull(&sdp_state->proposed_capabilities->connection_address)) {
+ if (!ast_sockaddr_isnull(&sdp_state->connection_address)) {
int port = ast_sockaddr_port(address);
- ast_sockaddr_copy(address, &sdp_state->proposed_capabilities->connection_address);
+ ast_sockaddr_copy(address, &sdp_state->connection_address);
ast_sockaddr_set_port(address, port);
}
return 0;
}
-const struct ast_stream_topology *ast_sdp_state_get_joint_topology(
- const struct ast_sdp_state *sdp_state)
+int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_state,
+ int stream_index, struct ast_sockaddr *address)
{
+ struct sdp_state_stream *stream_state;
+
ast_assert(sdp_state != NULL);
+ ast_assert(address != NULL);
- if (sdp_state->negotiated_capabilities) {
- return sdp_state->negotiated_capabilities->topology;
+ stream_state = sdp_state_get_stream(sdp_state, stream_index);
+ if (!stream_state) {
+ return -1;
}
- return sdp_state->proposed_capabilities->topology;
+ return sdp_state_stream_get_connection_address(sdp_state, stream_state, address);
+}
+
+const struct ast_stream_topology *ast_sdp_state_get_joint_topology(
+ const struct ast_sdp_state *sdp_state)
+{
+ const struct sdp_state_capabilities *capabilities;
+
+ capabilities = sdp_state_get_joint_capabilities(sdp_state);
+ return capabilities->topology;
}
const struct ast_stream_topology *ast_sdp_state_get_local_topology(
@@ -549,50 +871,185 @@ const struct ast_sdp_options *ast_sdp_state_get_options(
return sdp_state->options;
}
+static struct ast_stream *decline_stream(enum ast_media_type type, const char *name)
+{
+ struct ast_stream *stream;
+
+ if (!name) {
+ name = ast_codec_media_type2str(type);
+ }
+ stream = ast_stream_alloc(name, type);
+ if (!stream) {
+ return NULL;
+ }
+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+ return stream;
+}
+
+/*!
+ * \brief Merge an update stream into a local stream.
+ *
+ * \param options SDP Options
+ * \param update An updated stream
+ *
+ * \retval NULL An error occurred
+ * \retval non-NULL The joint stream created
+ */
+static struct ast_stream *merge_local_stream(const struct ast_sdp_options *options,
+ const struct ast_stream *update)
+{
+ struct ast_stream *joint_stream;
+ struct ast_format_cap *joint_cap;
+ struct ast_format_cap *allowed_cap;
+ struct ast_format_cap *update_cap;
+ enum ast_stream_state joint_state;
+
+ joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!joint_cap) {
+ return NULL;
+ }
+
+ update_cap = ast_stream_get_formats(update);
+ allowed_cap = ast_sdp_options_get_format_cap_type(options,
+ ast_stream_get_type(update));
+ if (allowed_cap && update_cap) {
+ struct ast_str *allowed_buf = ast_str_alloca(128);
+ struct ast_str *update_buf = ast_str_alloca(128);
+ struct ast_str *joint_buf = ast_str_alloca(128);
+
+ ast_format_cap_get_compatible(allowed_cap, update_cap, joint_cap);
+ ast_debug(3,
+ "Filtered update '%s' with allowed '%s' to get joint '%s'. Joint has %zu formats\n",
+ ast_format_cap_get_names(update_cap, &update_buf),
+ ast_format_cap_get_names(allowed_cap, &allowed_buf),
+ ast_format_cap_get_names(joint_cap, &joint_buf),
+ ast_format_cap_count(joint_cap));
+ }
+
+ /* Determine the joint stream state */
+ joint_state = AST_STREAM_STATE_REMOVED;
+ if (ast_stream_get_state(update) != AST_STREAM_STATE_REMOVED
+ && ast_format_cap_count(joint_cap)) {
+ joint_state = AST_STREAM_STATE_SENDRECV;
+ }
+
+ joint_stream = ast_stream_alloc(ast_stream_get_name(update),
+ ast_stream_get_type(update));
+ if (joint_stream) {
+ ast_stream_set_state(joint_stream, joint_state);
+ if (joint_state != AST_STREAM_STATE_REMOVED) {
+ ast_stream_set_formats(joint_stream, joint_cap);
+ }
+ }
+
+ ao2_ref(joint_cap, -1);
+
+ return joint_stream;
+}
+
/*!
- * \brief Merge two streams into a joint stream.
+ * \brief Merge a remote stream into a local stream.
*
- * \param local Our local stream
+ * \param sdp_state
+ * \param local Our local stream (NULL if creating new stream)
+ * \param locally_held Nonzero if the local stream is held
* \param remote A remote stream
*
* \retval NULL An error occurred
* \retval non-NULL The joint stream created
*/
-static struct ast_stream *merge_streams(const struct ast_stream *local,
+static struct ast_stream *merge_remote_stream(const struct ast_sdp_state *sdp_state,
+ const struct ast_stream *local, unsigned int locally_held,
const struct ast_stream *remote)
{
struct ast_stream *joint_stream;
struct ast_format_cap *joint_cap;
struct ast_format_cap *local_cap;
struct ast_format_cap *remote_cap;
- struct ast_str *local_buf = ast_str_alloca(128);
- struct ast_str *remote_buf = ast_str_alloca(128);
- struct ast_str *joint_buf = ast_str_alloca(128);
-
- joint_stream = ast_stream_alloc(ast_codec_media_type2str(ast_stream_get_type(remote)),
- ast_stream_get_type(remote));
- if (!joint_stream) {
- return NULL;
- }
+ const char *joint_name;
+ enum ast_stream_state joint_state;
+ enum ast_stream_state remote_state;
joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
if (!joint_cap) {
- ast_stream_free(joint_stream);
return NULL;
}
- local_cap = ast_stream_get_formats(local);
remote_cap = ast_stream_get_formats(remote);
+ if (local) {
+ local_cap = ast_stream_get_formats(local);
+ } else {
+ local_cap = ast_sdp_options_get_format_cap_type(sdp_state->options,
+ ast_stream_get_type(remote));
+ }
+ if (local_cap && remote_cap) {
+ struct ast_str *local_buf = ast_str_alloca(128);
+ struct ast_str *remote_buf = ast_str_alloca(128);
+ struct ast_str *joint_buf = ast_str_alloca(128);
+
+ ast_format_cap_get_compatible(local_cap, remote_cap, joint_cap);
+ ast_debug(3,
+ "Combined local '%s' with remote '%s' to get joint '%s'. Joint has %zu formats\n",
+ ast_format_cap_get_names(local_cap, &local_buf),
+ ast_format_cap_get_names(remote_cap, &remote_buf),
+ ast_format_cap_get_names(joint_cap, &joint_buf),
+ ast_format_cap_count(joint_cap));
+ }
+
+ /* Determine the joint stream state */
+ remote_state = ast_stream_get_state(remote);
+ joint_state = AST_STREAM_STATE_REMOVED;
+ if ((!local || ast_stream_get_state(local) != AST_STREAM_STATE_REMOVED)
+ && ast_format_cap_count(joint_cap)) {
+ if (sdp_state->locally_held || locally_held) {
+ switch (remote_state) {
+ case AST_STREAM_STATE_REMOVED:
+ break;
+ case AST_STREAM_STATE_INACTIVE:
+ joint_state = AST_STREAM_STATE_INACTIVE;
+ break;
+ case AST_STREAM_STATE_SENDRECV:
+ joint_state = AST_STREAM_STATE_SENDONLY;
+ break;
+ case AST_STREAM_STATE_SENDONLY:
+ joint_state = AST_STREAM_STATE_INACTIVE;
+ break;
+ case AST_STREAM_STATE_RECVONLY:
+ joint_state = AST_STREAM_STATE_SENDONLY;
+ break;
+ }
+ } else {
+ switch (remote_state) {
+ case AST_STREAM_STATE_REMOVED:
+ break;
+ case AST_STREAM_STATE_INACTIVE:
+ joint_state = AST_STREAM_STATE_RECVONLY;
+ break;
+ case AST_STREAM_STATE_SENDRECV:
+ joint_state = AST_STREAM_STATE_SENDRECV;
+ break;
+ case AST_STREAM_STATE_SENDONLY:
+ joint_state = AST_STREAM_STATE_RECVONLY;
+ break;
+ case AST_STREAM_STATE_RECVONLY:
+ joint_state = AST_STREAM_STATE_SENDRECV;
+ break;
+ }
+ }
+ }
- ast_format_cap_get_compatible(local_cap, remote_cap, joint_cap);
-
- ast_debug(3, "Combined local '%s' with remote '%s' to get joint '%s'. Joint has %zu formats\n",
- ast_format_cap_get_names(local_cap, &local_buf),
- ast_format_cap_get_names(remote_cap, &remote_buf),
- ast_format_cap_get_names(joint_cap, &joint_buf),
- ast_format_cap_count(joint_cap));
-
- ast_stream_set_formats(joint_stream, joint_cap);
+ if (local) {
+ joint_name = ast_stream_get_name(local);
+ } else {
+ joint_name = ast_codec_media_type2str(ast_stream_get_type(remote));
+ }
+ joint_stream = ast_stream_alloc(joint_name, ast_stream_get_type(remote));
+ if (joint_stream) {
+ ast_stream_set_state(joint_stream, joint_state);
+ if (joint_state != AST_STREAM_STATE_REMOVED) {
+ ast_stream_set_formats(joint_stream, joint_cap);
+ }
+ }
ao2_ref(joint_cap, -1);
@@ -600,103 +1057,916 @@ static struct ast_stream *merge_streams(const struct ast_stream *local,
}
/*!
- * \brief Get a local stream that corresponds with a remote stream.
+ * \internal
+ * \brief Determine if a merged topology should be rejected.
+ * \since 15.0.0
*
- * \param local The local topology
- * \param media_type The type of stream we are looking for
- * \param[in,out] media_indices Keeps track of where to start searching in the topology
+ * \param topology What topology to determine if we reject
*
- * \retval -1 No corresponding stream found
- * \retval index The corresponding stream index
+ * \retval 0 if not rejected.
+ * \retval non-zero if rejected.
*/
-static int get_corresponding_index(const struct ast_stream_topology *local,
- enum ast_media_type media_type, int *media_indices)
+static int sdp_topology_is_rejected(struct ast_stream_topology *topology)
{
- int i;
+ int idx;
+ struct ast_stream *stream;
+
+ for (idx = ast_stream_topology_get_count(topology); idx--;) {
+ stream = ast_stream_topology_get_stream(topology, idx);
+ if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) {
+ /* At least one stream is not declined */
+ return 0;
+ }
+ }
+
+ /* All streams are declined */
+ return 1;
+}
+
+static void sdp_state_stream_copy_common(struct sdp_state_stream *dst, const struct sdp_state_stream *src)
+{
+ ast_sockaddr_copy(&dst->connection_address,
+ &src->connection_address);
+ /* Explicitly does not copy the local or remote hold states. */
+ dst->t38_local_params = src->t38_local_params;
+}
- for (i = media_indices[media_type]; i < ast_stream_topology_get_count(local); ++i) {
- struct ast_stream *candidate;
+static void sdp_state_stream_copy(struct sdp_state_stream *dst, const struct sdp_state_stream *src)
+{
+ *dst = *src;
- candidate = ast_stream_topology_get_stream(local, i);
- if (ast_stream_get_type(candidate) == media_type) {
- media_indices[media_type] = i + 1;
- return i;
+ switch (dst->type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ ao2_bump(dst->rtp);
+ break;
+ case AST_MEDIA_TYPE_IMAGE:
+ ao2_bump(dst->udptl);
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_TEXT:
+ case AST_MEDIA_TYPE_END:
+ break;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Initialize an int vector and default the contents to the member index.
+ * \since 15.0.0
+ *
+ * \param vect Vetctor to initialize and set to default values.
+ * \param size Size of the vector to setup.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int sdp_vect_idx_init(struct ast_vector_int *vect, size_t size)
+{
+ int idx;
+
+ if (AST_VECTOR_INIT(vect, size)) {
+ return -1;
+ }
+ for (idx = 0; idx < size; ++idx) {
+ AST_VECTOR_APPEND(vect, idx);
+ }
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Compare stream types for sort order.
+ * \since 15.0.0
+ *
+ * \param left Stream parameter on left
+ * \param right Stream parameter on right
+ *
+ * \retval <0 left stream sorts first.
+ * \retval =0 streams match.
+ * \retval >0 right stream sorts first.
+ */
+static int sdp_stream_cmp_by_type(const struct ast_stream *left, const struct ast_stream *right)
+{
+ enum ast_media_type left_type = ast_stream_get_type(left);
+ enum ast_media_type right_type = ast_stream_get_type(right);
+
+ /* Treat audio and image as the same for T.38 support */
+ if (left_type == AST_MEDIA_TYPE_IMAGE) {
+ left_type = AST_MEDIA_TYPE_AUDIO;
+ }
+ if (right_type == AST_MEDIA_TYPE_IMAGE) {
+ right_type = AST_MEDIA_TYPE_AUDIO;
+ }
+
+ return left_type - right_type;
+}
+
+/*!
+ * \internal
+ * \brief Compare stream names and types for sort order.
+ * \since 15.0.0
+ *
+ * \param left Stream parameter on left
+ * \param right Stream parameter on right
+ *
+ * \retval <0 left stream sorts first.
+ * \retval =0 streams match.
+ * \retval >0 right stream sorts first.
+ */
+static int sdp_stream_cmp_by_name(const struct ast_stream *left, const struct ast_stream *right)
+{
+ int cmp;
+ const char *left_name;
+
+ left_name = ast_stream_get_name(left);
+ cmp = strcmp(left_name, ast_stream_get_name(right));
+ if (!cmp) {
+ cmp = sdp_stream_cmp_by_type(left, right);
+ if (!cmp) {
+ /* Are the stream names real or type names which aren't matchable? */
+ if (ast_strlen_zero(left_name)
+ || !strcmp(left_name, ast_codec_media_type2str(ast_stream_get_type(left)))
+ || !strcmp(left_name, ast_codec_media_type2str(ast_stream_get_type(right)))) {
+ /* The streams don't actually have real names */
+ cmp = -1;
+ }
}
}
+ return cmp;
+}
- /* No stream of the type left in the topology */
- media_indices[media_type] = i;
- return -1;
+/*!
+ * \internal
+ * \brief Merge topology streams by the match function.
+ * \since 15.0.0
+ *
+ * \param sdp_state
+ * \param current_topology Topology to update with state.
+ * \param update_topology Topology to merge into the current topology.
+ * \param current_vect Stream index vector of remaining current_topology streams.
+ * \param update_vect Stream index vector of remaining update_topology streams.
+ * \param backfill_candidate Array of flags marking current_topology streams
+ * that can be reused for a different stream.
+ * \param match Stream comparison function to identify corresponding streams
+ * between the current_topology and update_topology.
+ * \param merged_topology Output topology of merged streams.
+ * \param compact_streams TRUE if backfill and limit number of streams.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int sdp_merge_streams_match(
+ const struct ast_sdp_state *sdp_state,
+ const struct ast_stream_topology *current_topology,
+ const struct ast_stream_topology *update_topology,
+ struct ast_vector_int *current_vect,
+ struct ast_vector_int *update_vect,
+ char backfill_candidate[],
+ int (*match)(const struct ast_stream *left, const struct ast_stream *right),
+ struct ast_stream_topology *merged_topology,
+ int compact_streams)
+{
+ struct ast_stream *current_stream;
+ struct ast_stream *update_stream;
+ int current_idx;
+ int update_idx;
+ int idx;
+
+ for (current_idx = 0; current_idx < AST_VECTOR_SIZE(current_vect);) {
+ idx = AST_VECTOR_GET(current_vect, current_idx);
+ current_stream = ast_stream_topology_get_stream(current_topology, idx);
+
+ for (update_idx = 0; update_idx < AST_VECTOR_SIZE(update_vect); ++update_idx) {
+ idx = AST_VECTOR_GET(update_vect, update_idx);
+ update_stream = ast_stream_topology_get_stream(update_topology, idx);
+
+ if (match(current_stream, update_stream)) {
+ continue;
+ }
+
+ if (!compact_streams
+ || ast_stream_get_state(current_stream) != AST_STREAM_STATE_REMOVED
+ || ast_stream_get_state(update_stream) != AST_STREAM_STATE_REMOVED) {
+ struct ast_stream *merged_stream;
+
+ merged_stream = merge_local_stream(sdp_state->options, update_stream);
+ if (!merged_stream) {
+ return -1;
+ }
+ idx = AST_VECTOR_GET(current_vect, current_idx);
+ ast_stream_topology_set_stream(merged_topology, idx, merged_stream);
+
+ /*
+ * The current_stream cannot be considered a backfill_candidate
+ * anymore since it got updated.
+ *
+ * XXX It could be argued that if the declined status didn't
+ * change because the merged_stream became declined then we
+ * shouldn't remove the stream slot as a backfill_candidate
+ * and we shouldn't update the merged_topology stream. If we
+ * then backfilled the stream we would likely mess up the core
+ * if it is matching streams by type since the core attempted
+ * to update the stream with an incompatible stream. Any
+ * backfilled streams could cause a stream type ordering
+ * problem. However, we do need to reclaim declined stream
+ * slots sometime.
+ */
+ backfill_candidate[idx] = 0;
+ }
+
+ AST_VECTOR_REMOVE_ORDERED(current_vect, current_idx);
+ AST_VECTOR_REMOVE_ORDERED(update_vect, update_idx);
+ goto matched_next;
+ }
+
+ ++current_idx;
+matched_next:;
+ }
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Merge the current local topology with an updated topology.
+ * \since 15.0.0
+ *
+ * \param sdp_state
+ * \param current_topology Topology to update with state.
+ * \param update_topology Topology to merge into the current topology.
+ * \param compact_streams TRUE if backfill and limit number of streams.
+ *
+ * \retval merged topology on success.
+ * \retval NULL on failure.
+ */
+static struct ast_stream_topology *merge_local_topologies(
+ const struct ast_sdp_state *sdp_state,
+ const struct ast_stream_topology *current_topology,
+ const struct ast_stream_topology *update_topology,
+ int compact_streams)
+{
+ struct ast_stream_topology *merged_topology;
+ struct ast_stream *current_stream;
+ struct ast_stream *update_stream;
+ struct ast_stream *merged_stream;
+ struct ast_vector_int current_vect;
+ struct ast_vector_int update_vect;
+ int current_idx = ast_stream_topology_get_count(current_topology);
+ int update_idx;
+ int idx;
+ char backfill_candidate[current_idx];
+
+ memset(backfill_candidate, 0, current_idx);
+
+ if (compact_streams) {
+ /* Limit matching consideration to the maximum allowed live streams. */
+ idx = ast_sdp_options_get_max_streams(sdp_state->options);
+ if (idx < current_idx) {
+ current_idx = idx;
+ }
+ }
+ if (sdp_vect_idx_init(&current_vect, current_idx)) {
+ return NULL;
+ }
+
+ if (sdp_vect_idx_init(&update_vect, ast_stream_topology_get_count(update_topology))) {
+ AST_VECTOR_FREE(&current_vect);
+ return NULL;
+ }
+
+ merged_topology = ast_stream_topology_clone(current_topology);
+ if (!merged_topology) {
+ goto fail;
+ }
+
+ /*
+ * Remove any unsupported current streams from match consideration
+ * and mark potential backfill candidates.
+ */
+ for (current_idx = AST_VECTOR_SIZE(&current_vect); current_idx--;) {
+ idx = AST_VECTOR_GET(&current_vect, current_idx);
+ current_stream = ast_stream_topology_get_stream(current_topology, idx);
+ if (ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED
+ && compact_streams) {
+ /* The declined stream is a potential backfill candidate */
+ backfill_candidate[idx] = 1;
+ }
+ if (sdp_is_stream_type_supported(ast_stream_get_type(current_stream))) {
+ continue;
+ }
+ /* Unsupported current streams should always be declined */
+ ast_assert(ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED);
+
+ AST_VECTOR_REMOVE_ORDERED(&current_vect, current_idx);
+ }
+
+ /* Remove any unsupported update streams from match consideration. */
+ for (update_idx = AST_VECTOR_SIZE(&update_vect); update_idx--;) {
+ idx = AST_VECTOR_GET(&update_vect, update_idx);
+ update_stream = ast_stream_topology_get_stream(update_topology, idx);
+ if (sdp_is_stream_type_supported(ast_stream_get_type(update_stream))) {
+ continue;
+ }
+
+ AST_VECTOR_REMOVE_ORDERED(&update_vect, update_idx);
+ }
+
+ /* Match by stream name and type */
+ if (sdp_merge_streams_match(sdp_state, current_topology, update_topology,
+ &current_vect, &update_vect, backfill_candidate, sdp_stream_cmp_by_name,
+ merged_topology, compact_streams)) {
+ goto fail;
+ }
+
+ /* Match by stream type */
+ if (sdp_merge_streams_match(sdp_state, current_topology, update_topology,
+ &current_vect, &update_vect, backfill_candidate, sdp_stream_cmp_by_type,
+ merged_topology, compact_streams)) {
+ goto fail;
+ }
+
+ /* Decline unmatched current stream slots */
+ for (current_idx = AST_VECTOR_SIZE(&current_vect); current_idx--;) {
+ idx = AST_VECTOR_GET(&current_vect, current_idx);
+ current_stream = ast_stream_topology_get_stream(current_topology, idx);
+
+ if (ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED) {
+ /* Stream is already declined. */
+ continue;
+ }
+
+ merged_stream = decline_stream(ast_stream_get_type(current_stream),
+ ast_stream_get_name(current_stream));
+ if (!merged_stream) {
+ goto fail;
+ }
+ ast_stream_topology_set_stream(merged_topology, idx, merged_stream);
+ }
+
+ /* Backfill new update stream slots into pre-existing declined current stream slots */
+ while (AST_VECTOR_SIZE(&update_vect)) {
+ idx = ast_stream_topology_get_count(current_topology);
+ for (current_idx = 0; current_idx < idx; ++current_idx) {
+ if (backfill_candidate[current_idx]) {
+ break;
+ }
+ }
+ if (idx <= current_idx) {
+ /* No more backfill candidates remain. */
+ break;
+ }
+ /* There should only be backfill stream slots when we are compact_streams */
+ ast_assert(compact_streams);
+
+ idx = AST_VECTOR_GET(&update_vect, 0);
+ update_stream = ast_stream_topology_get_stream(update_topology, idx);
+ AST_VECTOR_REMOVE_ORDERED(&update_vect, 0);
+
+ if (ast_stream_get_state(update_stream) == AST_STREAM_STATE_REMOVED) {
+ /* New stream is already declined so don't bother adding it. */
+ continue;
+ }
+
+ merged_stream = merge_local_stream(sdp_state->options, update_stream);
+ if (!merged_stream) {
+ goto fail;
+ }
+ if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) {
+ /* New stream not compatible so don't bother adding it. */
+ ast_stream_free(merged_stream);
+ continue;
+ }
+
+ /* Add the new stream into the backfill stream slot. */
+ ast_stream_topology_set_stream(merged_topology, current_idx, merged_stream);
+ backfill_candidate[current_idx] = 0;
+ }
+
+ /* Append any remaining new update stream slots that can fit. */
+ while (AST_VECTOR_SIZE(&update_vect)
+ && (!compact_streams
+ || ast_stream_topology_get_count(merged_topology)
+ < ast_sdp_options_get_max_streams(sdp_state->options))) {
+ idx = AST_VECTOR_GET(&update_vect, 0);
+ update_stream = ast_stream_topology_get_stream(update_topology, idx);
+ AST_VECTOR_REMOVE_ORDERED(&update_vect, 0);
+
+ if (ast_stream_get_state(update_stream) == AST_STREAM_STATE_REMOVED) {
+ /* New stream is already declined so don't bother adding it. */
+ continue;
+ }
+
+ merged_stream = merge_local_stream(sdp_state->options, update_stream);
+ if (!merged_stream) {
+ goto fail;
+ }
+ if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) {
+ /* New stream not compatible so don't bother adding it. */
+ ast_stream_free(merged_stream);
+ continue;
+ }
+
+ /* Append the new update stream. */
+ if (ast_stream_topology_append_stream(merged_topology, merged_stream) < 0) {
+ ast_stream_free(merged_stream);
+ goto fail;
+ }
+ }
+
+ AST_VECTOR_FREE(&current_vect);
+ AST_VECTOR_FREE(&update_vect);
+ return merged_topology;
+
+fail:
+ ast_stream_topology_free(merged_topology);
+ AST_VECTOR_FREE(&current_vect);
+ AST_VECTOR_FREE(&update_vect);
+ return NULL;
}
/*!
- * XXX TODO The merge_capabilities() function needs to be split into
- * merging for new local topologies and new remote topologies. Also
- * the possibility of changing the stream types needs consideration.
- * Audio to video may or may not need us to keep the same RTP instance
- * because the stream position is still RTP. A new RTP instance would
- * cause us to change ports. Audio to image is definitely going to
- * happen for T.38.
- *
- * A new remote topology as an initial offer needs to dictate the
- * number of streams and the order. As a sdp_state option we may
- * allow creation of new active streams not defined by the current
- * local topology. A subsequent remote offer can change the stream
- * types and add streams. The sdp_state option could regulate
- * creation of new active streams here as well. An answer cannot
- * change stream types or the number of streams but can decline
- * streams. Any attempt to do so should report an error and possibly
- * disconnect the call.
- *
- * A local topology update needs to be merged differently. It cannot
- * reduce the number of streams already defined without violating the
- * SDP RFC. The local merge could take the new topology stream
- * verbatim and add declined streams to fill out any shortfall with
- * the exiting topology. This strategy is needed if we want to change
- * an audio stream to an image stream for T.38 fax and vice versa.
- * The local merge could take the new topology and map the streams to
- * the existing local topology. The new topology stream format caps
- * would be copied into the merged topology so we could change what
- * codecs are negotiated.
+ * \internal
+ * \brief Remove declined streams appended beyond orig_topology.
+ * \since 15.0.0
+ *
+ * \param sdp_state
+ * \param orig_topology Negotiated or initial topology.
+ * \param new_topology New proposed topology.
+ *
+ * \return Nothing
*/
+static void remove_appended_declined_streams(const struct ast_sdp_state *sdp_state,
+ const struct ast_stream_topology *orig_topology,
+ struct ast_stream_topology *new_topology)
+{
+ struct ast_stream *stream;
+ int orig_count;
+ int idx;
+
+ orig_count = ast_stream_topology_get_count(orig_topology);
+ for (idx = ast_stream_topology_get_count(new_topology); orig_count < idx;) {
+ --idx;
+ stream = ast_stream_topology_get_stream(new_topology, idx);
+ if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) {
+ continue;
+ }
+ ast_stream_topology_del_stream(new_topology, idx);
+ }
+}
+
/*!
- * \brief Merge existing stream capabilities and a new topology into joint capabilities.
+ * \internal
+ * \brief Setup a new state stream from a possibly existing state stream.
+ * \since 15.0.0
+ *
+ * \param sdp_state
+ * \param new_state_stream What state stream to setup
+ * \param old_state_stream Source of previous state stream information.
+ * May be NULL.
+ * \param new_type Type of the new state stream.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int setup_new_stream_capabilities(
+ const struct ast_sdp_state *sdp_state,
+ struct sdp_state_stream *new_state_stream,
+ struct sdp_state_stream *old_state_stream,
+ enum ast_media_type new_type)
+{
+ if (old_state_stream) {
+ /*
+ * Copy everything potentially useful for a new stream state type
+ * from the old stream of a possible different type.
+ */
+ sdp_state_stream_copy_common(new_state_stream, old_state_stream);
+ /* We also need to preserve the locally_held state for the new stream. */
+ new_state_stream->locally_held = old_state_stream->locally_held;
+ }
+ new_state_stream->type = new_type;
+
+ switch (new_type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ new_state_stream->rtp = create_rtp(sdp_state->options, new_type);
+ if (!new_state_stream->rtp) {
+ return -1;
+ }
+ break;
+ case AST_MEDIA_TYPE_IMAGE:
+ new_state_stream->udptl = create_udptl(sdp_state->options);
+ if (!new_state_stream->udptl) {
+ return -1;
+ }
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_TEXT:
+ case AST_MEDIA_TYPE_END:
+ break;
+ }
+ return 0;
+}
+
+/*!
+ * \brief Merge existing stream capabilities and a new topology.
*
* \param sdp_state The state needing capabilities merged
- * \param new_topology The new topology to base merged capabilities on
- * \param is_local If new_topology is a local update.
+ * \param new_topology The topology to merge with our proposed capabilities
*
* \details
+ *
* This is a bit complicated. The idea is that we already have some
* capabilities set, and we've now been confronted with a new stream
- * topology. We want to take what's been presented to us and merge
- * those new capabilities with our own.
- *
- * For each of the new streams, we try to find a corresponding stream
- * in our proposed capabilities. If we find one, then we get the
- * compatible formats of the two streams and create a new stream with
- * those formats set. We then will re-use the underlying media
- * instance (such as an RTP instance) on this merged stream.
- *
- * The is_local parameter determines whether we should attempt to
- * create new media instances. If we do not find a corresponding
- * stream, then we create a new one. If the is_local parameter is
- * true, this created stream is made a clone of the new stream, and a
- * media instance is created. If the is_local parameter is not true,
- * then the created stream has no formats set and no media instance is
- * created for it.
+ * topology from the system. We want to take what we had before and
+ * merge them with the new topology from the system.
+ *
+ * According to the RFC, stream slots can change their types only if
+ * they are carrying the same logical information or an offer is
+ * reusing a declined slot or new stream slots are added to the end
+ * of the list. Switching a stream from audio to T.38 makes sense
+ * because the stream slot is carrying the same information just in a
+ * different format.
+ *
+ * We can setup new streams offered by the system up to our
+ * configured maximum stream slots. New stream slots requested over
+ * the maximum are discarded.
+ *
+ * \retval NULL An error occurred
+ * \retval non-NULL The merged capabilities
+ */
+static struct sdp_state_capabilities *merge_local_capabilities(
+ const struct ast_sdp_state *sdp_state,
+ const struct ast_stream_topology *new_topology)
+{
+ const struct sdp_state_capabilities *current = sdp_state->proposed_capabilities;
+ struct sdp_state_capabilities *merged_capabilities;
+ int idx;
+
+ ast_assert(current != NULL);
+
+ merged_capabilities = ast_calloc(1, sizeof(*merged_capabilities));
+ if (!merged_capabilities) {
+ return NULL;
+ }
+
+ merged_capabilities->topology = merge_local_topologies(sdp_state, current->topology,
+ new_topology, 1);
+ if (!merged_capabilities->topology) {
+ goto fail;
+ }
+ sdp_state_cb_offerer_modify_topology(sdp_state, merged_capabilities->topology);
+ remove_appended_declined_streams(sdp_state, current->topology,
+ merged_capabilities->topology);
+
+ if (AST_VECTOR_INIT(&merged_capabilities->streams,
+ ast_stream_topology_get_count(merged_capabilities->topology))) {
+ goto fail;
+ }
+
+ for (idx = 0; idx < ast_stream_topology_get_count(merged_capabilities->topology); ++idx) {
+ struct sdp_state_stream *merged_state_stream;
+ struct sdp_state_stream *current_state_stream;
+ struct ast_stream *merged_stream;
+ struct ast_stream *current_stream;
+ enum ast_media_type merged_stream_type;
+ enum ast_media_type current_stream_type;
+
+ merged_state_stream = ast_calloc(1, sizeof(*merged_state_stream));
+ if (!merged_state_stream) {
+ goto fail;
+ }
+
+ merged_stream = ast_stream_topology_get_stream(merged_capabilities->topology, idx);
+ merged_stream_type = ast_stream_get_type(merged_stream);
+
+ if (idx < ast_stream_topology_get_count(current->topology)) {
+ current_state_stream = AST_VECTOR_GET(&current->streams, idx);
+ current_stream = ast_stream_topology_get_stream(current->topology, idx);
+ current_stream_type = ast_stream_get_type(current_stream);
+ } else {
+ /* The merged topology is adding a stream */
+ current_state_stream = NULL;
+ current_stream = NULL;
+ current_stream_type = AST_MEDIA_TYPE_UNKNOWN;
+ }
+
+ if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) {
+ if (current_state_stream) {
+ /* Copy everything potentially useful to a declined stream state. */
+ sdp_state_stream_copy_common(merged_state_stream, current_state_stream);
+ }
+ merged_state_stream->type = merged_stream_type;
+ } else if (!current_stream
+ || ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED) {
+ /* This is a new stream */
+ if (setup_new_stream_capabilities(sdp_state, merged_state_stream,
+ current_state_stream, merged_stream_type)) {
+ sdp_state_stream_free(merged_state_stream);
+ goto fail;
+ }
+ } else if (merged_stream_type == current_stream_type) {
+ /* Stream type is not changing. */
+ sdp_state_stream_copy(merged_state_stream, current_state_stream);
+ } else {
+ /*
+ * Stream type is changing. Need to replace the stream.
+ *
+ * Unsupported streams should already be handled earlier because
+ * they are always declined.
+ */
+ ast_assert(sdp_is_stream_type_supported(merged_stream_type));
+
+ /*
+ * XXX We might need to keep the old RTP instance if the new
+ * stream type is also RTP. We would just be changing between
+ * audio and video in that case. However we will create a new
+ * RTP instance anyway since its purpose has to be changing.
+ * Any RTP packets in flight from the old stream type might
+ * cause mischief.
+ */
+ if (setup_new_stream_capabilities(sdp_state, merged_state_stream,
+ current_state_stream, merged_stream_type)) {
+ sdp_state_stream_free(merged_state_stream);
+ goto fail;
+ }
+ }
+
+ if (AST_VECTOR_APPEND(&merged_capabilities->streams, merged_state_stream)) {
+ sdp_state_stream_free(merged_state_stream);
+ goto fail;
+ }
+ }
+
+ return merged_capabilities;
+
+fail:
+ sdp_state_capabilities_free(merged_capabilities);
+ return NULL;
+}
+
+static void merge_remote_stream_capabilities(
+ const struct ast_sdp_state *sdp_state,
+ struct sdp_state_stream *joint_state_stream,
+ struct sdp_state_stream *local_state_stream,
+ struct ast_stream *remote_stream)
+{
+ struct ast_rtp_codecs *codecs;
+
+ *joint_state_stream = *local_state_stream;
+ /*
+ * Need to explicitly set the type to the remote because we could
+ * be changing the type between audio and video.
+ */
+ joint_state_stream->type = ast_stream_get_type(remote_stream);
+
+ switch (joint_state_stream->type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ ao2_bump(joint_state_stream->rtp);
+ codecs = ast_stream_get_data(remote_stream, AST_STREAM_DATA_RTP_CODECS);
+ ast_assert(codecs != NULL);
+ if (sdp_state->role == SDP_ROLE_ANSWERER) {
+ /*
+ * Setup rx payload type mapping to prefer the mapping
+ * from the peer that the RFC says we SHOULD use.
+ */
+ ast_rtp_codecs_payloads_xover(codecs, codecs, NULL);
+ }
+ ast_rtp_codecs_payloads_copy(codecs,
+ ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance),
+ joint_state_stream->rtp->instance);
+ break;
+ case AST_MEDIA_TYPE_IMAGE:
+ joint_state_stream->udptl = ao2_bump(joint_state_stream->udptl);
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_TEXT:
+ case AST_MEDIA_TYPE_END:
+ break;
+ }
+}
+
+static int create_remote_stream_capabilities(
+ const struct ast_sdp_state *sdp_state,
+ struct sdp_state_stream *joint_state_stream,
+ struct sdp_state_stream *local_state_stream,
+ struct ast_stream *remote_stream)
+{
+ struct ast_rtp_codecs *codecs;
+
+ /* We can only create streams if we are the answerer */
+ ast_assert(sdp_state->role == SDP_ROLE_ANSWERER);
+
+ if (local_state_stream) {
+ /*
+ * Copy everything potentially useful for a new stream state type
+ * from the old stream of a possible different type.
+ */
+ sdp_state_stream_copy_common(joint_state_stream, local_state_stream);
+ /* We also need to preserve the locally_held state for the new stream. */
+ joint_state_stream->locally_held = local_state_stream->locally_held;
+ }
+ joint_state_stream->type = ast_stream_get_type(remote_stream);
+
+ switch (joint_state_stream->type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ joint_state_stream->rtp = create_rtp(sdp_state->options, joint_state_stream->type);
+ if (!joint_state_stream->rtp) {
+ return -1;
+ }
+
+ /*
+ * Setup rx payload type mapping to prefer the mapping
+ * from the peer that the RFC says we SHOULD use.
+ */
+ codecs = ast_stream_get_data(remote_stream, AST_STREAM_DATA_RTP_CODECS);
+ ast_assert(codecs != NULL);
+ ast_rtp_codecs_payloads_xover(codecs, codecs, NULL);
+ ast_rtp_codecs_payloads_copy(codecs,
+ ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance),
+ joint_state_stream->rtp->instance);
+ break;
+ case AST_MEDIA_TYPE_IMAGE:
+ joint_state_stream->udptl = create_udptl(sdp_state->options);
+ if (!joint_state_stream->udptl) {
+ return -1;
+ }
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_TEXT:
+ case AST_MEDIA_TYPE_END:
+ break;
+ }
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Create a joint topology from the remote topology.
+ * \since 15.0.0
+ *
+ * \param sdp_state The state needing capabilities merged.
+ * \param local Capabilities to merge the remote topology into.
+ * \param remote_topology The topology to merge with our local capabilities.
+ *
+ * \retval joint topology on success.
+ * \retval NULL on failure.
+ */
+static struct ast_stream_topology *merge_remote_topology(
+ const struct ast_sdp_state *sdp_state,
+ const struct sdp_state_capabilities *local,
+ const struct ast_stream_topology *remote_topology)
+{
+ struct ast_stream_topology *joint_topology;
+ int idx;
+
+ joint_topology = ast_stream_topology_alloc();
+ if (!joint_topology) {
+ return NULL;
+ }
+
+ for (idx = 0; idx < ast_stream_topology_get_count(remote_topology); ++idx) {
+ enum ast_media_type local_stream_type;
+ enum ast_media_type remote_stream_type;
+ struct ast_stream *remote_stream;
+ struct ast_stream *local_stream;
+ struct ast_stream *joint_stream;
+ struct sdp_state_stream *local_state_stream;
+
+ remote_stream = ast_stream_topology_get_stream(remote_topology, idx);
+ remote_stream_type = ast_stream_get_type(remote_stream);
+
+ if (idx < ast_stream_topology_get_count(local->topology)) {
+ local_state_stream = AST_VECTOR_GET(&local->streams, idx);
+ local_stream = ast_stream_topology_get_stream(local->topology, idx);
+ local_stream_type = ast_stream_get_type(local_stream);
+ } else {
+ /* The remote is adding a stream slot */
+ local_state_stream = NULL;
+ local_stream = NULL;
+ local_stream_type = AST_MEDIA_TYPE_UNKNOWN;
+
+ if (sdp_state->role != SDP_ROLE_ANSWERER) {
+ /* Remote cannot add a new stream slot in an answer SDP */
+ ast_debug(1,
+ "Bad. Ignoring new %s stream slot remote answer SDP trying to add.\n",
+ ast_codec_media_type2str(remote_stream_type));
+ continue;
+ }
+ }
+
+ if (local_stream
+ && ast_stream_get_state(local_stream) != AST_STREAM_STATE_REMOVED) {
+ if (remote_stream_type == local_stream_type) {
+ /* Stream type is not changing. */
+ joint_stream = merge_remote_stream(sdp_state, local_stream,
+ local_state_stream->locally_held, remote_stream);
+ } else if (sdp_state->role == SDP_ROLE_ANSWERER) {
+ /* Stream type is changing. */
+ joint_stream = merge_remote_stream(sdp_state, NULL,
+ local_state_stream->locally_held, remote_stream);
+ } else {
+ /*
+ * Remote cannot change the stream type we offered.
+ * Mark as declined.
+ */
+ ast_debug(1,
+ "Bad. Remote answer SDP trying to change the stream type from %s to %s.\n",
+ ast_codec_media_type2str(local_stream_type),
+ ast_codec_media_type2str(remote_stream_type));
+ joint_stream = decline_stream(local_stream_type,
+ ast_stream_get_name(local_stream));
+ }
+ } else {
+ /* Local stream is either dead/declined or nonexistent. */
+ if (sdp_state->role == SDP_ROLE_ANSWERER) {
+ if (sdp_is_stream_type_supported(remote_stream_type)
+ && ast_stream_get_state(remote_stream) != AST_STREAM_STATE_REMOVED
+ && idx < ast_sdp_options_get_max_streams(sdp_state->options)) {
+ /* Try to create the new stream */
+ joint_stream = merge_remote_stream(sdp_state, NULL,
+ local_state_stream ? local_state_stream->locally_held : 0,
+ remote_stream);
+ } else {
+ const char *stream_name;
+
+ /* Decline the remote stream. */
+ if (local_stream
+ && local_stream_type == remote_stream_type) {
+ /* Preserve the previous stream name */
+ stream_name = ast_stream_get_name(local_stream);
+ } else {
+ stream_name = NULL;
+ }
+ joint_stream = decline_stream(remote_stream_type, stream_name);
+ }
+ } else {
+ /* Decline the stream. */
+ if (DEBUG_ATLEAST(1)
+ && ast_stream_get_state(remote_stream) != AST_STREAM_STATE_REMOVED) {
+ /*
+ * Remote cannot request a new stream in place of a declined
+ * stream in an answer SDP.
+ */
+ ast_log(LOG_DEBUG,
+ "Bad. Remote answer SDP trying to use a declined stream slot for %s.\n",
+ ast_codec_media_type2str(remote_stream_type));
+ }
+ joint_stream = decline_stream(local_stream_type,
+ ast_stream_get_name(local_stream));
+ }
+ }
+
+ if (!joint_stream) {
+ goto fail;
+ }
+ if (ast_stream_topology_append_stream(joint_topology, joint_stream) < 0) {
+ ast_stream_free(joint_stream);
+ goto fail;
+ }
+ }
+
+ return joint_topology;
+
+fail:
+ ast_stream_topology_free(joint_topology);
+ return NULL;
+}
+
+/*!
+ * \brief Merge our stream capabilities and a remote topology into joint capabilities.
+ *
+ * \param sdp_state The state needing capabilities merged
+ * \param remote_topology The topology to merge with our proposed capabilities
+ *
+ * \details
+ * This is a bit complicated. The idea is that we already have some
+ * capabilities set, and we've now been confronted with a stream
+ * topology from the remote end. We want to take what's been
+ * presented to us and merge those new capabilities with our own.
+ *
+ * According to the RFC, stream slots can change their types only if
+ * they are carrying the same logical information or an offer is
+ * reusing a declined slot or new stream slots are added to the end
+ * of the list. Switching a stream from audio to T.38 makes sense
+ * because the stream slot is carrying the same information just in a
+ * different format.
+ *
+ * When we are the answerer we can setup new streams offered by the
+ * remote up to our configured maximum stream slots. New stream
+ * slots offered over the maximum are unconditionally declined.
*
* \retval NULL An error occurred
* \retval non-NULL The merged capabilities
*/
-static struct sdp_state_capabilities *merge_capabilities(const struct ast_sdp_state *sdp_state,
- const struct ast_stream_topology *new_topology, int is_local)
+static struct sdp_state_capabilities *merge_remote_capabilities(
+ const struct ast_sdp_state *sdp_state,
+ const struct ast_stream_topology *remote_topology)
{
const struct sdp_state_capabilities *local = sdp_state->proposed_capabilities;
struct sdp_state_capabilities *joint_capabilities;
- int media_indices[AST_MEDIA_TYPE_END] = {0};
- int i;
- static const char dummy_name[] = "dummy";
+ int idx;
ast_assert(local != NULL);
@@ -705,150 +1975,131 @@ static struct sdp_state_capabilities *merge_capabilities(const struct ast_sdp_st
return NULL;
}
- joint_capabilities->topology = ast_stream_topology_alloc();
+ joint_capabilities->topology = merge_remote_topology(sdp_state, local, remote_topology);
if (!joint_capabilities->topology) {
goto fail;
}
- if (AST_VECTOR_INIT(&joint_capabilities->streams, AST_VECTOR_SIZE(&local->streams))) {
+ if (sdp_state->role == SDP_ROLE_ANSWERER) {
+ sdp_state_cb_answerer_modify_topology(sdp_state, joint_capabilities->topology);
+ }
+ idx = ast_stream_topology_get_count(joint_capabilities->topology);
+ if (AST_VECTOR_INIT(&joint_capabilities->streams, idx)) {
goto fail;
}
- ast_sockaddr_copy(&joint_capabilities->connection_address, &local->connection_address);
- for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) {
- enum ast_media_type new_stream_type;
- struct ast_stream *new_stream;
+ for (idx = 0; idx < ast_stream_topology_get_count(remote_topology); ++idx) {
+ enum ast_media_type local_stream_type;
+ enum ast_media_type remote_stream_type;
+ struct ast_stream *remote_stream;
struct ast_stream *local_stream;
struct ast_stream *joint_stream;
+ struct sdp_state_stream *local_state_stream;
struct sdp_state_stream *joint_state_stream;
- int local_index;
joint_state_stream = ast_calloc(1, sizeof(*joint_state_stream));
if (!joint_state_stream) {
goto fail;
}
- new_stream = ast_stream_topology_get_stream(new_topology, i);
- new_stream_type = ast_stream_get_type(new_stream);
+ remote_stream = ast_stream_topology_get_stream(remote_topology, idx);
+ remote_stream_type = ast_stream_get_type(remote_stream);
- local_index = get_corresponding_index(local->topology, new_stream_type, media_indices);
- if (0 <= local_index) {
- local_stream = ast_stream_topology_get_stream(local->topology, local_index);
- if (!strcmp(ast_stream_get_name(local_stream), dummy_name)) {
- /* The local stream is a non-exixtent dummy stream. */
- local_stream = NULL;
- }
+ if (idx < ast_stream_topology_get_count(local->topology)) {
+ local_state_stream = AST_VECTOR_GET(&local->streams, idx);
+ local_stream = ast_stream_topology_get_stream(local->topology, idx);
+ local_stream_type = ast_stream_get_type(local_stream);
} else {
+ /* The remote is adding a stream slot */
+ local_state_stream = NULL;
local_stream = NULL;
- }
- if (local_stream) {
- struct sdp_state_stream *local_state_stream;
- struct ast_rtp_codecs *codecs;
+ local_stream_type = AST_MEDIA_TYPE_UNKNOWN;
- if (is_local) {
- /* Replace the local stream with the new local stream. */
- joint_stream = ast_stream_clone(new_stream, NULL);
- } else {
- joint_stream = merge_streams(local_stream, new_stream);
- }
- if (!joint_stream) {
+ if (sdp_state->role != SDP_ROLE_ANSWERER) {
+ /* Remote cannot add a new stream slot in an answer SDP */
sdp_state_stream_free(joint_state_stream);
- goto fail;
- }
-
- local_state_stream = AST_VECTOR_GET(&local->streams, local_index);
- joint_state_stream->type = local_state_stream->type;
-
- switch (joint_state_stream->type) {
- case AST_MEDIA_TYPE_AUDIO:
- case AST_MEDIA_TYPE_VIDEO:
- joint_state_stream->rtp = ao2_bump(local_state_stream->rtp);
- if (is_local) {
- break;
- }
- codecs = ast_stream_get_data(new_stream, AST_STREAM_DATA_RTP_CODECS);
- ast_assert(codecs != NULL);
- if (sdp_state->role == SDP_ROLE_ANSWERER) {
- /*
- * Setup rx payload type mapping to prefer the mapping
- * from the peer that the RFC says we SHOULD use.
- */
- ast_rtp_codecs_payloads_xover(codecs, codecs, NULL);
- }
- ast_rtp_codecs_payloads_copy(codecs,
- ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance),
- joint_state_stream->rtp->instance);
- break;
- case AST_MEDIA_TYPE_IMAGE:
- joint_state_stream->udptl = ao2_bump(local_state_stream->udptl);
- joint_state_stream->t38_local_params = local_state_stream->t38_local_params;
- break;
- case AST_MEDIA_TYPE_UNKNOWN:
- case AST_MEDIA_TYPE_TEXT:
- case AST_MEDIA_TYPE_END:
break;
}
+ }
- if (!ast_sockaddr_isnull(&local_state_stream->connection_address)) {
- ast_sockaddr_copy(&joint_state_stream->connection_address,
- &local_state_stream->connection_address);
+ joint_stream = ast_stream_topology_get_stream(joint_capabilities->topology,
+ idx);
+
+ if (local_stream
+ && ast_stream_get_state(local_stream) != AST_STREAM_STATE_REMOVED) {
+ if (ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED) {
+ /* Copy everything potentially useful to a declined stream state. */
+ sdp_state_stream_copy_common(joint_state_stream, local_state_stream);
+
+ joint_state_stream->type = ast_stream_get_type(joint_stream);
+ } else if (remote_stream_type == local_stream_type) {
+ /* Stream type is not changing. */
+ merge_remote_stream_capabilities(sdp_state, joint_state_stream,
+ local_state_stream, remote_stream);
+ ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream));
} else {
- ast_sockaddr_setnull(&joint_state_stream->connection_address);
- }
- joint_state_stream->locally_held = local_state_stream->locally_held;
- } else if (is_local) {
- /* We don't have a stream state that corresponds to the stream in the new topology, so
- * create a stream state as appropriate.
- */
- joint_stream = ast_stream_clone(new_stream, NULL);
- if (!joint_stream) {
- sdp_state_stream_free(joint_state_stream);
- goto fail;
- }
-
- switch (new_stream_type) {
- case AST_MEDIA_TYPE_AUDIO:
- case AST_MEDIA_TYPE_VIDEO:
- joint_state_stream->rtp = create_rtp(sdp_state->options,
- new_stream_type);
- if (!joint_state_stream->rtp) {
- ast_stream_free(joint_stream);
- sdp_state_stream_free(joint_state_stream);
- goto fail;
- }
- break;
- case AST_MEDIA_TYPE_IMAGE:
- joint_state_stream->udptl = create_udptl(sdp_state->options);
- if (!joint_state_stream->udptl) {
- ast_stream_free(joint_stream);
+ /*
+ * Stream type is changing. Need to replace the stream.
+ *
+ * XXX We might need to keep the old RTP instance if the new
+ * stream type is also RTP. We would just be changing between
+ * audio and video in that case. However we will create a new
+ * RTP instance anyway since its purpose has to be changing.
+ * Any RTP packets in flight from the old stream type might
+ * cause mischief.
+ */
+ if (create_remote_stream_capabilities(sdp_state, joint_state_stream,
+ local_state_stream, remote_stream)) {
sdp_state_stream_free(joint_state_stream);
goto fail;
}
- break;
- case AST_MEDIA_TYPE_UNKNOWN:
- case AST_MEDIA_TYPE_TEXT:
- case AST_MEDIA_TYPE_END:
- break;
+ ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream));
}
- ast_sockaddr_setnull(&joint_state_stream->connection_address);
- joint_state_stream->locally_held = 0;
} else {
- /* We don't have a stream that corresponds to the stream in the new topology. Create a
- * dummy stream to go in its place so that the resulting SDP created will contain
- * the stream but will have no port or codecs set
- */
- joint_stream = ast_stream_alloc(dummy_name, new_stream_type);
- if (!joint_stream) {
- sdp_state_stream_free(joint_state_stream);
- goto fail;
+ /* Local stream is either dead/declined or nonexistent. */
+ if (sdp_state->role == SDP_ROLE_ANSWERER) {
+ if (ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED) {
+ if (local_state_stream) {
+ /* Copy everything potentially useful to a declined stream state. */
+ sdp_state_stream_copy_common(joint_state_stream, local_state_stream);
+ }
+ joint_state_stream->type = ast_stream_get_type(joint_stream);
+ } else {
+ /* Try to create the new stream */
+ if (create_remote_stream_capabilities(sdp_state, joint_state_stream,
+ local_state_stream, remote_stream)) {
+ sdp_state_stream_free(joint_state_stream);
+ goto fail;
+ }
+ ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream));
+ }
+ } else {
+ /* Decline the stream. */
+ ast_assert(ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED);
+ if (local_state_stream) {
+ /* Copy everything potentially useful to a declined stream state. */
+ sdp_state_stream_copy_common(joint_state_stream, local_state_stream);
+ }
+ joint_state_stream->type = ast_stream_get_type(joint_stream);
}
}
- if (ast_stream_topology_append_stream(joint_capabilities->topology, joint_stream) < 0) {
- ast_stream_free(joint_stream);
- sdp_state_stream_free(joint_state_stream);
- goto fail;
+ /* Determine if the remote placed the stream on hold. */
+ joint_state_stream->remotely_held = 0;
+ if (ast_stream_get_state(joint_stream) != AST_STREAM_STATE_REMOVED) {
+ enum ast_stream_state remote_state;
+
+ remote_state = ast_stream_get_state(remote_stream);
+ switch (remote_state) {
+ case AST_STREAM_STATE_INACTIVE:
+ case AST_STREAM_STATE_SENDONLY:
+ joint_state_stream->remotely_held = 1;
+ break;
+ default:
+ break;
+ }
}
+
if (AST_VECTOR_APPEND(&joint_capabilities->streams, joint_state_stream)) {
sdp_state_stream_free(joint_state_stream);
goto fail;
@@ -1003,11 +2254,6 @@ static void update_rtp_after_merge(const struct ast_sdp_state *state,
struct ast_sdp_c_line *c_line;
struct ast_sockaddr *addrs;
- if (!rtp) {
- /* This is a dummy stream */
- return;
- }
-
c_line = remote_m_line->c_line;
if (!c_line) {
c_line = remote_sdp->c_line;
@@ -1063,11 +2309,6 @@ static void update_udptl_after_merge(const struct ast_sdp_state *state, struct s
unsigned int fax_max_datagram;
struct ast_sockaddr *addrs;
- if (!udptl) {
- /* This is a dummy stream */
- return;
- }
-
a_line = ast_sdp_m_find_attribute(remote_m_line, "t38faxmaxdatagram", -1);
if (!a_line) {
a_line = ast_sdp_m_find_attribute(remote_m_line, "t38maxdatagram", -1);
@@ -1107,6 +2348,49 @@ static void update_udptl_after_merge(const struct ast_sdp_state *state, struct s
}
}
+static void sdp_apply_negotiated_state(struct ast_sdp_state *sdp_state)
+{
+ struct sdp_state_capabilities *capabilities = sdp_state->negotiated_capabilities;
+ int idx;
+
+ if (!capabilities) {
+ /* Nothing to apply */
+ return;
+ }
+
+ sdp_state_cb_preapply_topology(sdp_state, capabilities->topology);
+ for (idx = 0; idx < AST_VECTOR_SIZE(&capabilities->streams); ++idx) {
+ struct sdp_state_stream *state_stream;
+ struct ast_stream *stream;
+
+ stream = ast_stream_topology_get_stream(capabilities->topology, idx);
+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
+ /* Stream is declined */
+ continue;
+ }
+
+ state_stream = AST_VECTOR_GET(&capabilities->streams, idx);
+ switch (ast_stream_get_type(stream)) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ update_rtp_after_merge(sdp_state, state_stream->rtp, sdp_state->options,
+ sdp_state->remote_sdp, ast_sdp_get_m(sdp_state->remote_sdp, idx));
+ break;
+ case AST_MEDIA_TYPE_IMAGE:
+ update_udptl_after_merge(sdp_state, state_stream->udptl, sdp_state->options,
+ sdp_state->remote_sdp, ast_sdp_get_m(sdp_state->remote_sdp, idx));
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_TEXT:
+ case AST_MEDIA_TYPE_END:
+ /* All unsupported streams are declined */
+ ast_assert(0);
+ break;
+ }
+ }
+ sdp_state_cb_postapply_topology(sdp_state, capabilities->topology);
+}
+
static void set_negotiated_capabilities(struct ast_sdp_state *sdp_state,
struct sdp_state_capabilities *new_capabilities)
{
@@ -1125,6 +2409,81 @@ static void set_proposed_capabilities(struct ast_sdp_state *sdp_state,
sdp_state_capabilities_free(old_capabilities);
}
+/*!
+ * \internal
+ * \brief Copy the new capabilities into the proposed capabilities.
+ * \since 15.0.0
+ *
+ * \param sdp_state The current SDP state
+ * \param new_capabilities Capabilities to copy
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int update_proposed_capabilities(struct ast_sdp_state *sdp_state,
+ struct sdp_state_capabilities *new_capabilities)
+{
+ struct sdp_state_capabilities *proposed_capabilities;
+ int idx;
+
+ proposed_capabilities = ast_calloc(1, sizeof(*proposed_capabilities));
+ if (!proposed_capabilities) {
+ return -1;
+ }
+
+ proposed_capabilities->topology = ast_stream_topology_clone(new_capabilities->topology);
+ if (!proposed_capabilities->topology) {
+ goto fail;
+ }
+
+ if (AST_VECTOR_INIT(&proposed_capabilities->streams,
+ AST_VECTOR_SIZE(&new_capabilities->streams))) {
+ goto fail;
+ }
+
+ for (idx = 0; idx < AST_VECTOR_SIZE(&new_capabilities->streams); ++idx) {
+ struct sdp_state_stream *proposed_state_stream;
+ struct sdp_state_stream *new_state_stream;
+
+ proposed_state_stream = ast_calloc(1, sizeof(*proposed_state_stream));
+ if (!proposed_state_stream) {
+ goto fail;
+ }
+
+ new_state_stream = AST_VECTOR_GET(&new_capabilities->streams, idx);
+ *proposed_state_stream = *new_state_stream;
+
+ switch (proposed_state_stream->type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ ao2_bump(proposed_state_stream->rtp);
+ break;
+ case AST_MEDIA_TYPE_IMAGE:
+ ao2_bump(proposed_state_stream->udptl);
+ break;
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_TEXT:
+ case AST_MEDIA_TYPE_END:
+ break;
+ }
+
+ /* This is explicitly never set on the proposed capabilities struct */
+ proposed_state_stream->remotely_held = 0;
+
+ if (AST_VECTOR_APPEND(&proposed_capabilities->streams, proposed_state_stream)) {
+ sdp_state_stream_free(proposed_state_stream);
+ goto fail;
+ }
+ }
+
+ set_proposed_capabilities(sdp_state, proposed_capabilities);
+ return 0;
+
+fail:
+ sdp_state_capabilities_free(proposed_capabilities);
+ return -1;
+}
+
static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_state,
const struct sdp_state_capabilities *capabilities);
@@ -1132,65 +2491,47 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta
* \brief Merge SDPs into a joint SDP.
*
* This function is used to take a remote SDP and merge it with our local
- * capabilities to produce a new local SDP. After creating the new local SDP,
- * it then iterates through media instances and updates them as necessary. For
+ * capabilities to produce a new local SDP. After creating the new local SDP,
+ * it then iterates through media instances and updates them as necessary. For
* instance, if a specific RTP feature is supported by both us and the far end,
* then we can ensure that the feature is enabled.
*
* \param sdp_state The current SDP state
- * \retval -1 Failure
+ *
* \retval 0 Success
+ * \retval -1 Failure
+ * Use ast_sdp_state_is_offer_rejected() to see if the offer SDP was rejected.
*/
static int merge_sdps(struct ast_sdp_state *sdp_state, const struct ast_sdp *remote_sdp)
{
struct sdp_state_capabilities *joint_capabilities;
struct ast_stream_topology *remote_capabilities;
- int i;
remote_capabilities = ast_get_topology_from_sdp(remote_sdp,
- sdp_state->options->g726_non_standard);
+ ast_sdp_options_get_g726_non_standard(sdp_state->options));
if (!remote_capabilities) {
return -1;
}
- joint_capabilities = merge_capabilities(sdp_state, remote_capabilities, 0);
+ joint_capabilities = merge_remote_capabilities(sdp_state, remote_capabilities);
ast_stream_topology_free(remote_capabilities);
if (!joint_capabilities) {
return -1;
}
- set_negotiated_capabilities(sdp_state, joint_capabilities);
-
- if (sdp_state->local_sdp) {
- ast_sdp_free(sdp_state->local_sdp);
- sdp_state->local_sdp = NULL;
- }
-
- sdp_state->local_sdp = sdp_create_from_state(sdp_state, joint_capabilities);
- if (!sdp_state->local_sdp) {
- return -1;
+ if (sdp_state->role == SDP_ROLE_ANSWERER) {
+ sdp_state->remote_offer_rejected =
+ sdp_topology_is_rejected(joint_capabilities->topology) ? 1 : 0;
+ if (sdp_state->remote_offer_rejected) {
+ sdp_state_capabilities_free(joint_capabilities);
+ return -1;
+ }
}
+ set_negotiated_capabilities(sdp_state, joint_capabilities);
- for (i = 0; i < AST_VECTOR_SIZE(&joint_capabilities->streams); ++i) {
- struct sdp_state_stream *state_stream;
-
- state_stream = AST_VECTOR_GET(&joint_capabilities->streams, i);
+ ao2_cleanup(sdp_state->remote_sdp);
+ sdp_state->remote_sdp = ao2_bump((struct ast_sdp *) remote_sdp);
- switch (ast_stream_get_type(ast_stream_topology_get_stream(joint_capabilities->topology, i))) {
- case AST_MEDIA_TYPE_AUDIO:
- case AST_MEDIA_TYPE_VIDEO:
- update_rtp_after_merge(sdp_state, state_stream->rtp, sdp_state->options,
- remote_sdp, ast_sdp_get_m(remote_sdp, i));
- break;
- case AST_MEDIA_TYPE_IMAGE:
- update_udptl_after_merge(sdp_state, state_stream->udptl, sdp_state->options,
- remote_sdp, ast_sdp_get_m(remote_sdp, i));
- break;
- case AST_MEDIA_TYPE_UNKNOWN:
- case AST_MEDIA_TYPE_TEXT:
- case AST_MEDIA_TYPE_END:
- break;
- }
- }
+ sdp_apply_negotiated_state(sdp_state);
return 0;
}
@@ -1199,10 +2540,43 @@ const struct ast_sdp *ast_sdp_state_get_local_sdp(struct ast_sdp_state *sdp_stat
{
ast_assert(sdp_state != NULL);
- if (sdp_state->role == SDP_ROLE_NOT_SET) {
+ switch (sdp_state->role) {
+ case SDP_ROLE_NOT_SET:
ast_assert(sdp_state->local_sdp == NULL);
sdp_state->role = SDP_ROLE_OFFERER;
+
+ if (sdp_state->pending_topology_update) {
+ struct sdp_state_capabilities *capabilities;
+
+ /* We have a topology update to perform before generating the offer */
+ capabilities = merge_local_capabilities(sdp_state,
+ sdp_state->pending_topology_update);
+ if (!capabilities) {
+ break;
+ }
+ ast_stream_topology_free(sdp_state->pending_topology_update);
+ sdp_state->pending_topology_update = NULL;
+ set_proposed_capabilities(sdp_state, capabilities);
+ }
+
+ /*
+ * Allow the system to configure the topology streams
+ * before we create the offer SDP.
+ */
+ sdp_state_cb_offerer_config_topology(sdp_state,
+ sdp_state->proposed_capabilities->topology);
+
sdp_state->local_sdp = sdp_create_from_state(sdp_state, sdp_state->proposed_capabilities);
+ break;
+ case SDP_ROLE_OFFERER:
+ break;
+ case SDP_ROLE_ANSWERER:
+ if (!sdp_state->local_sdp
+ && sdp_state->negotiated_capabilities
+ && !sdp_state->remote_offer_rejected) {
+ sdp_state->local_sdp = sdp_create_from_state(sdp_state, sdp_state->negotiated_capabilities);
+ }
+ break;
}
return sdp_state->local_sdp;
@@ -1242,35 +2616,63 @@ int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, cons
return -1;
}
ret = ast_sdp_state_set_remote_sdp(sdp_state, sdp);
- ast_sdp_free(sdp);
+ ao2_ref(sdp, -1);
return ret;
}
-int ast_sdp_state_reset(struct ast_sdp_state *sdp_state)
+int ast_sdp_state_is_offer_rejected(struct ast_sdp_state *sdp_state)
+{
+ return sdp_state->remote_offer_rejected;
+}
+
+int ast_sdp_state_is_offerer(struct ast_sdp_state *sdp_state)
+{
+ return sdp_state->role == SDP_ROLE_OFFERER;
+}
+
+int ast_sdp_state_is_answerer(struct ast_sdp_state *sdp_state)
+{
+ return sdp_state->role == SDP_ROLE_ANSWERER;
+}
+
+int ast_sdp_state_restart_negotiations(struct ast_sdp_state *sdp_state)
{
ast_assert(sdp_state != NULL);
- ast_sdp_free(sdp_state->local_sdp);
+ ao2_cleanup(sdp_state->local_sdp);
sdp_state->local_sdp = NULL;
- set_proposed_capabilities(sdp_state, NULL);
-
sdp_state->role = SDP_ROLE_NOT_SET;
+ sdp_state->remote_offer_rejected = 0;
+
+ if (sdp_state->negotiated_capabilities) {
+ update_proposed_capabilities(sdp_state, sdp_state->negotiated_capabilities);
+ }
return 0;
}
-int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *streams)
+int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *topology)
{
- struct sdp_state_capabilities *capabilities;
+ struct ast_stream_topology *merged_topology;
+
ast_assert(sdp_state != NULL);
- ast_assert(streams != NULL);
+ ast_assert(topology != NULL);
- capabilities = merge_capabilities(sdp_state, streams, 1);
- if (!capabilities) {
- return -1;
+ if (sdp_state->pending_topology_update) {
+ merged_topology = merge_local_topologies(sdp_state,
+ sdp_state->pending_topology_update, topology, 0);
+ if (!merged_topology) {
+ return -1;
+ }
+ ast_stream_topology_free(sdp_state->pending_topology_update);
+ sdp_state->pending_topology_update = merged_topology;
+ } else {
+ sdp_state->pending_topology_update = ast_stream_topology_clone(topology);
+ if (!sdp_state->pending_topology_update) {
+ return -1;
+ }
}
- set_proposed_capabilities(sdp_state, capabilities);
return 0;
}
@@ -1280,9 +2682,9 @@ void ast_sdp_state_set_local_address(struct ast_sdp_state *sdp_state, struct ast
ast_assert(sdp_state != NULL);
if (!address) {
- ast_sockaddr_setnull(&sdp_state->proposed_capabilities->connection_address);
+ ast_sockaddr_setnull(&sdp_state->connection_address);
} else {
- ast_sockaddr_copy(&sdp_state->proposed_capabilities->connection_address, address);
+ ast_sockaddr_copy(&sdp_state->connection_address, address);
}
}
@@ -1306,18 +2708,37 @@ int ast_sdp_state_set_connection_address(struct ast_sdp_state *sdp_state, int st
return 0;
}
+void ast_sdp_state_set_global_locally_held(struct ast_sdp_state *sdp_state, unsigned int locally_held)
+{
+ ast_assert(sdp_state != NULL);
+
+ sdp_state->locally_held = locally_held ? 1 : 0;
+}
+
+unsigned int ast_sdp_state_get_global_locally_held(const struct ast_sdp_state *sdp_state)
+{
+ ast_assert(sdp_state != NULL);
+
+ return sdp_state->locally_held;
+}
+
void ast_sdp_state_set_locally_held(struct ast_sdp_state *sdp_state,
int stream_index, unsigned int locally_held)
{
struct sdp_state_stream *stream_state;
ast_assert(sdp_state != NULL);
- stream_state = sdp_state_get_stream(sdp_state, stream_index);
- if (!stream_state) {
- return;
+ locally_held = locally_held ? 1 : 0;
+
+ stream_state = sdp_state_get_joint_stream(sdp_state, stream_index);
+ if (stream_state) {
+ stream_state->locally_held = locally_held;
}
- stream_state->locally_held = locally_held;
+ stream_state = sdp_state_get_stream(sdp_state, stream_index);
+ if (stream_state) {
+ stream_state->locally_held = locally_held;
+ }
}
unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state,
@@ -1326,7 +2747,7 @@ unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_stat
struct sdp_state_stream *stream_state;
ast_assert(sdp_state != NULL);
- stream_state = sdp_state_get_stream(sdp_state, stream_index);
+ stream_state = sdp_state_get_joint_stream(sdp_state, stream_index);
if (!stream_state) {
return 0;
}
@@ -1334,6 +2755,21 @@ unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_stat
return stream_state->locally_held;
}
+unsigned int ast_sdp_state_get_remotely_held(const struct ast_sdp_state *sdp_state,
+ int stream_index)
+{
+ struct sdp_state_stream *stream_state;
+
+ ast_assert(sdp_state != NULL);
+
+ stream_state = sdp_state_get_joint_stream(sdp_state, stream_index);
+ if (!stream_state) {
+ return 0;
+ }
+
+ return stream_state->remotely_held;
+}
+
void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state,
int stream_index, struct ast_control_t38_parameters *params)
{
@@ -1341,11 +2777,9 @@ void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state,
ast_assert(sdp_state != NULL && params != NULL);
stream_state = sdp_state_get_stream(sdp_state, stream_index);
- if (!stream_state) {
- return;
+ if (stream_state) {
+ stream_state->t38_local_params = *params;
}
-
- stream_state->t38_local_params = *params;
}
/*!
@@ -1379,8 +2813,161 @@ static void add_ssrc_attributes(struct ast_sdp_m_line *m_line, const struct ast_
ast_sdp_m_add_a(m_line, a_line);
}
+/*!
+ * \internal
+ * \brief Create a declined m-line from a remote requested stream.
+ * \since 15.0.0
+ *
+ * \details
+ * Using the last received remote SDP create a declined stream
+ * m-line for the requested stream. The stream may be unsupported.
+ *
+ * \param sdp Our SDP under construction to append the declined stream.
+ * \param sdp_state
+ * \param stream_index Which remote SDP stream we are declining.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int sdp_add_m_from_declined_remote_stream(struct ast_sdp *sdp,
+ const struct ast_sdp_state *sdp_state, int stream_index)
+{
+ const struct ast_sdp_m_line *m_line_remote;
+ struct ast_sdp_m_line *m_line;
+ int idx;
+
+ ast_assert(sdp && sdp_state && sdp_state->remote_sdp);
+ ast_assert(stream_index < ast_sdp_get_m_count(sdp_state->remote_sdp));
+
+ /*
+ * The only way we can generate a declined unsupported stream
+ * m-line is if the remote offered it to us.
+ */
+ m_line_remote = ast_sdp_get_m(sdp_state->remote_sdp, stream_index);
+
+ /* Copy remote SDP stream m-line except for port number. */
+ m_line = ast_sdp_m_alloc(m_line_remote->type, 0, m_line_remote->port_count,
+ m_line_remote->proto, NULL);
+ if (!m_line) {
+ return -1;
+ }
+
+ /* Copy any m-line payload strings from the remote SDP */
+ for (idx = 0; idx < ast_sdp_m_get_payload_count(m_line_remote); ++idx) {
+ const struct ast_sdp_payload *payload_remote;
+ struct ast_sdp_payload *payload;
+
+ payload_remote = ast_sdp_m_get_payload(m_line_remote, idx);
+ payload = ast_sdp_payload_alloc(payload_remote->fmt);
+ if (!payload) {
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+ if (ast_sdp_m_add_payload(m_line, payload)) {
+ ast_sdp_payload_free(payload);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+ }
+
+ if (ast_sdp_add_m(sdp, m_line)) {
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*!
+ * \internal
+ * \brief Create a declined m-line for our SDP stream.
+ * \since 15.0.0
+ *
+ * \param sdp Our SDP under construction to append the declined stream.
+ * \param sdp_state
+ * \param type Stream type we are declining.
+ * \param stream_index Which remote SDP stream we are declining.
+ *
+ * \retval 0 on success.
+ * \retval -1 on failure.
+ */
+static int sdp_add_m_from_declined_stream(struct ast_sdp *sdp,
+ const struct ast_sdp_state *sdp_state, enum ast_media_type type, int stream_index)
+{
+ struct ast_sdp_m_line *m_line;
+ const char *proto;
+ const char *fmt;
+ struct ast_sdp_payload *payload;
+
+ if (sdp_state->role == SDP_ROLE_ANSWERER) {
+ /* We are declining the remote stream or it is still declined. */
+ return sdp_add_m_from_declined_remote_stream(sdp, sdp_state, stream_index);
+ }
+
+ /* Send declined remote stream in our offer if the type matches. */
+ if (sdp_state->remote_sdp
+ && stream_index < ast_sdp_get_m_count(sdp_state->remote_sdp)) {
+ if (!sdp_is_stream_type_supported(type)
+ || !strcasecmp(ast_sdp_get_m(sdp_state->remote_sdp, stream_index)->type,
+ ast_codec_media_type2str(type))) {
+ /* Stream is still declined */
+ return sdp_add_m_from_declined_remote_stream(sdp, sdp_state, stream_index);
+ }
+ }
+
+ /* Build a new declined stream in our offer. */
+ switch (type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ case AST_MEDIA_TYPE_VIDEO:
+ proto = "RTP/AVP";
+ break;
+ case AST_MEDIA_TYPE_IMAGE:
+ proto = "udptl";
+ break;
+ default:
+ /* Stream type not supported */
+ ast_assert(0);
+ return -1;
+ }
+ m_line = ast_sdp_m_alloc(ast_codec_media_type2str(type), 0, 1, proto, NULL);
+ if (!m_line) {
+ return -1;
+ }
+
+ /* Add a dummy static payload type */
+ switch (type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ fmt = "0"; /* ulaw */
+ break;
+ case AST_MEDIA_TYPE_VIDEO:
+ fmt = "31"; /* H.261 */
+ break;
+ case AST_MEDIA_TYPE_IMAGE:
+ fmt = "t38"; /* T.38 */
+ break;
+ default:
+ /* Stream type not supported */
+ ast_assert(0);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+ payload = ast_sdp_payload_alloc(fmt);
+ if (!payload || ast_sdp_m_add_payload(m_line, payload)) {
+ ast_sdp_payload_free(payload);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+
+ if (ast_sdp_add_m(sdp, m_line)) {
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+
+ return 0;
+}
+
static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_state *sdp_state,
- const struct ast_sdp_options *options, const struct sdp_state_capabilities *capabilities, int stream_index)
+ const struct sdp_state_capabilities *capabilities, int stream_index)
{
struct ast_stream *stream;
struct ast_sdp_m_line *m_line;
@@ -1395,15 +2982,19 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s
struct sdp_state_stream *stream_state;
struct ast_rtp_instance *rtp;
struct ast_sdp_a_line *a_line;
+ const struct ast_sdp_options *options;
+ const char *direction;
stream = ast_stream_topology_get_stream(capabilities->topology, stream_index);
- ast_assert(sdp && options && stream);
+ ast_assert(sdp && sdp_state && stream);
+ options = sdp_state->options;
caps = ast_stream_get_formats(stream);
stream_state = AST_VECTOR_GET(&capabilities->streams, stream_index);
- if (stream_state->rtp && caps && ast_format_cap_count(caps)) {
+ if (stream_state->rtp && caps && ast_format_cap_count(caps)
+ && AST_STREAM_STATE_REMOVED != ast_stream_get_state(stream)) {
rtp = stream_state->rtp->instance;
} else {
/* This is a disabled stream */
@@ -1413,7 +3004,7 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s
if (rtp) {
struct ast_sockaddr address_rtp;
- if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_rtp)) {
+ if (sdp_state_stream_get_connection_address(sdp_state, stream_state, &address_rtp)) {
return -1;
}
rtp_port = ast_sockaddr_port(&address_rtp);
@@ -1421,129 +3012,119 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s
rtp_port = 0;
}
- m_line = ast_sdp_m_alloc(
- ast_codec_media_type2str(ast_stream_get_type(stream)),
- rtp_port, 1,
+ media_type = ast_stream_get_type(stream);
+ if (!rtp_port) {
+ /* Declined/disabled stream */
+ return sdp_add_m_from_declined_stream(sdp, sdp_state, media_type, stream_index);
+ }
+
+ /* Stream is not declined/disabled */
+ m_line = ast_sdp_m_alloc(ast_codec_media_type2str(media_type), rtp_port, 1,
options->encryption != AST_SDP_ENCRYPTION_DISABLED ? "RTP/SAVP" : "RTP/AVP",
NULL);
if (!m_line) {
return -1;
}
- if (rtp_port) {
- /* Stream is not declined/disabled */
- for (i = 0; i < ast_format_cap_count(caps); i++) {
- struct ast_format *format = ast_format_cap_get_format(caps, i);
-
- rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(rtp), 1,
- format, 0);
- if (rtp_code == -1) {
- ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n",
- ast_format_get_name(format));
- ao2_ref(format, -1);
- continue;
- }
-
- if (ast_sdp_m_add_format(m_line, options, rtp_code, 1, format, 0)) {
- ast_sdp_m_free(m_line);
- ao2_ref(format, -1);
- return -1;
- }
-
- if (ast_format_get_maximum_ms(format)
- && ((ast_format_get_maximum_ms(format) < max_packet_size)
- || !max_packet_size)) {
- max_packet_size = ast_format_get_maximum_ms(format);
- }
+ for (i = 0; i < ast_format_cap_count(caps); i++) {
+ struct ast_format *format = ast_format_cap_get_format(caps, i);
+ rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(rtp), 1,
+ format, 0);
+ if (rtp_code == -1) {
+ ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n",
+ ast_format_get_name(format));
ao2_ref(format, -1);
+ continue;
}
- media_type = ast_stream_get_type(stream);
- if (media_type != AST_MEDIA_TYPE_VIDEO
- && (options->dtmf == AST_SDP_DTMF_RFC_4733 || options->dtmf == AST_SDP_DTMF_AUTO)) {
- i = AST_RTP_DTMF;
- rtp_code = ast_rtp_codecs_payload_code(
- ast_rtp_instance_get_codecs(rtp), 0, NULL, i);
- if (-1 < rtp_code) {
- if (ast_sdp_m_add_format(m_line, options, rtp_code, 0, NULL, i)) {
- ast_sdp_m_free(m_line);
- return -1;
- }
-
- snprintf(tmp, sizeof(tmp), "%d 0-16", rtp_code);
- a_line = ast_sdp_a_alloc("fmtp", tmp);
- if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
- ast_sdp_a_free(a_line);
- ast_sdp_m_free(m_line);
- return -1;
- }
- }
+ if (ast_sdp_m_add_format(m_line, options, rtp_code, 1, format, 0)) {
+ ast_sdp_m_free(m_line);
+ ao2_ref(format, -1);
+ return -1;
}
- /* If ptime is set add it as an attribute */
- min_packet_size = ast_rtp_codecs_get_framing(ast_rtp_instance_get_codecs(rtp));
- if (!min_packet_size) {
- min_packet_size = ast_format_cap_get_framing(caps);
+ if (ast_format_get_maximum_ms(format)
+ && ((ast_format_get_maximum_ms(format) < max_packet_size)
+ || !max_packet_size)) {
+ max_packet_size = ast_format_get_maximum_ms(format);
}
- if (min_packet_size) {
- snprintf(tmp, sizeof(tmp), "%d", min_packet_size);
- a_line = ast_sdp_a_alloc("ptime", tmp);
- if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
- ast_sdp_a_free(a_line);
+ ao2_ref(format, -1);
+ }
+
+ if (media_type != AST_MEDIA_TYPE_VIDEO
+ && (options->dtmf == AST_SDP_DTMF_RFC_4733 || options->dtmf == AST_SDP_DTMF_AUTO)) {
+ i = AST_RTP_DTMF;
+ rtp_code = ast_rtp_codecs_payload_code(
+ ast_rtp_instance_get_codecs(rtp), 0, NULL, i);
+ if (-1 < rtp_code) {
+ if (ast_sdp_m_add_format(m_line, options, rtp_code, 0, NULL, i)) {
ast_sdp_m_free(m_line);
return -1;
}
- }
- if (max_packet_size) {
- snprintf(tmp, sizeof(tmp), "%d", max_packet_size);
- a_line = ast_sdp_a_alloc("maxptime", tmp);
+ snprintf(tmp, sizeof(tmp), "%d 0-16", rtp_code);
+ a_line = ast_sdp_a_alloc("fmtp", tmp);
if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
ast_sdp_a_free(a_line);
ast_sdp_m_free(m_line);
return -1;
}
}
+ }
- a_line = ast_sdp_a_alloc(ast_sdp_state_get_locally_held(sdp_state, stream_index)
- ? "sendonly" : "sendrecv", "");
+ /* If ptime is set add it as an attribute */
+ min_packet_size = ast_rtp_codecs_get_framing(ast_rtp_instance_get_codecs(rtp));
+ if (!min_packet_size) {
+ min_packet_size = ast_format_cap_get_framing(caps);
+ }
+ if (min_packet_size) {
+ snprintf(tmp, sizeof(tmp), "%d", min_packet_size);
+
+ a_line = ast_sdp_a_alloc("ptime", tmp);
if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
ast_sdp_a_free(a_line);
ast_sdp_m_free(m_line);
return -1;
}
+ }
- add_ssrc_attributes(m_line, options, rtp);
- } else {
- /* Declined/disabled stream */
- struct ast_sdp_payload *payload;
- const char *fmt;
+ if (max_packet_size) {
+ snprintf(tmp, sizeof(tmp), "%d", max_packet_size);
+ a_line = ast_sdp_a_alloc("maxptime", tmp);
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+ }
- /*
- * Add a static payload type placeholder to the declined/disabled stream.
- *
- * XXX We should use the default payload type in the received offer but
- * we don't have that available.
- */
- switch (ast_stream_get_type(stream)) {
- default:
- case AST_MEDIA_TYPE_AUDIO:
- fmt = "0"; /* ulaw */
- break;
- case AST_MEDIA_TYPE_VIDEO:
- fmt = "31"; /* H.261 */
- break;
+ if (sdp_state->locally_held || stream_state->locally_held) {
+ if (stream_state->remotely_held) {
+ direction = "inactive";
+ } else {
+ direction = "sendonly";
}
- payload = ast_sdp_payload_alloc(fmt);
- if (!payload || ast_sdp_m_add_payload(m_line, payload)) {
- ast_sdp_payload_free(payload);
+ } else {
+ if (stream_state->remotely_held) {
+ direction = "recvonly";
+ } else {
+ /* Default is "sendrecv" */
+ direction = NULL;
+ }
+ }
+ if (direction) {
+ a_line = ast_sdp_a_alloc(direction, "");
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
ast_sdp_m_free(m_line);
return -1;
}
}
+ add_ssrc_attributes(m_line, options, rtp);
+
if (ast_sdp_add_m(sdp, m_line)) {
ast_sdp_m_free(m_line);
return -1;
@@ -1574,11 +3155,12 @@ static unsigned int t38_get_rate(enum ast_control_t38_rate rate)
}
static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp_state *sdp_state,
- const struct ast_sdp_options *options, const struct sdp_state_capabilities *capabilities, int stream_index)
+ const struct sdp_state_capabilities *capabilities, int stream_index)
{
struct ast_stream *stream;
struct ast_sdp_m_line *m_line;
struct ast_sdp_payload *payload;
+ enum ast_media_type media_type;
char tmp[64];
struct sdp_state_udptl *udptl;
struct ast_sdp_a_line *a_line;
@@ -1587,10 +3169,11 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp
stream = ast_stream_topology_get_stream(capabilities->topology, stream_index);
- ast_assert(sdp && options && stream);
+ ast_assert(sdp && sdp_state && stream);
stream_state = AST_VECTOR_GET(&capabilities->streams, stream_index);
- if (stream_state->udptl) {
+ if (stream_state->udptl
+ && AST_STREAM_STATE_REMOVED != ast_stream_get_state(stream)) {
udptl = stream_state->udptl;
} else {
/* This is a disabled stream */
@@ -1600,7 +3183,7 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp
if (udptl) {
struct ast_sockaddr address_udptl;
- if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_udptl)) {
+ if (sdp_state_stream_get_connection_address(sdp_state, stream_state, &address_udptl)) {
return -1;
}
udptl_port = ast_sockaddr_port(&address_udptl);
@@ -1608,9 +3191,15 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp
udptl_port = 0;
}
- m_line = ast_sdp_m_alloc(
- ast_codec_media_type2str(ast_stream_get_type(stream)),
- udptl_port, 1, "udptl", NULL);
+ media_type = ast_stream_get_type(stream);
+ if (!udptl_port) {
+ /* Declined/disabled stream */
+ return sdp_add_m_from_declined_stream(sdp, sdp_state, media_type, stream_index);
+ }
+
+ /* Stream is not declined/disabled */
+ m_line = ast_sdp_m_alloc(ast_codec_media_type2str(media_type), udptl_port, 1,
+ "udptl", NULL);
if (!m_line) {
return -1;
}
@@ -1622,100 +3211,95 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp
return -1;
}
- if (udptl_port) {
- /* Stream is not declined/disabled */
- stream_state = sdp_state_get_stream(sdp_state, stream_index);
+ snprintf(tmp, sizeof(tmp), "%u", stream_state->t38_local_params.version);
+ a_line = ast_sdp_a_alloc("T38FaxVersion", tmp);
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
+
+ snprintf(tmp, sizeof(tmp), "%u", t38_get_rate(stream_state->t38_local_params.rate));
+ a_line = ast_sdp_a_alloc("T38FaxMaxBitRate", tmp);
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
- snprintf(tmp, sizeof(tmp), "%u", stream_state->t38_local_params.version);
- a_line = ast_sdp_a_alloc("T38FaxVersion", tmp);
+ if (stream_state->t38_local_params.fill_bit_removal) {
+ a_line = ast_sdp_a_alloc("T38FaxFillBitRemoval", "");
if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
ast_sdp_a_free(a_line);
ast_sdp_m_free(m_line);
return -1;
}
+ }
- snprintf(tmp, sizeof(tmp), "%u", t38_get_rate(stream_state->t38_local_params.rate));
- a_line = ast_sdp_a_alloc("T38FaxMaxBitRate", tmp);
+ if (stream_state->t38_local_params.transcoding_mmr) {
+ a_line = ast_sdp_a_alloc("T38FaxTranscodingMMR", "");
if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
ast_sdp_a_free(a_line);
ast_sdp_m_free(m_line);
return -1;
}
+ }
- if (stream_state->t38_local_params.fill_bit_removal) {
- a_line = ast_sdp_a_alloc("T38FaxFillBitRemoval", "");
- if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
- ast_sdp_a_free(a_line);
- ast_sdp_m_free(m_line);
- return -1;
- }
+ if (stream_state->t38_local_params.transcoding_jbig) {
+ a_line = ast_sdp_a_alloc("T38FaxTranscodingJBIG", "");
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
}
+ }
- if (stream_state->t38_local_params.transcoding_mmr) {
- a_line = ast_sdp_a_alloc("T38FaxTranscodingMMR", "");
- if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
- ast_sdp_a_free(a_line);
- ast_sdp_m_free(m_line);
- return -1;
- }
+ switch (stream_state->t38_local_params.rate_management) {
+ case AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF:
+ a_line = ast_sdp_a_alloc("T38FaxRateManagement", "transferredTCF");
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
}
-
- if (stream_state->t38_local_params.transcoding_jbig) {
- a_line = ast_sdp_a_alloc("T38FaxTranscodingJBIG", "");
- if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
- ast_sdp_a_free(a_line);
- ast_sdp_m_free(m_line);
- return -1;
- }
+ break;
+ case AST_T38_RATE_MANAGEMENT_LOCAL_TCF:
+ a_line = ast_sdp_a_alloc("T38FaxRateManagement", "localTCF");
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
}
+ break;
+ }
- switch (stream_state->t38_local_params.rate_management) {
- case AST_T38_RATE_MANAGEMENT_TRANSFERRED_TCF:
- a_line = ast_sdp_a_alloc("T38FaxRateManagement", "transferredTCF");
- if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
- ast_sdp_a_free(a_line);
- ast_sdp_m_free(m_line);
- return -1;
- }
- break;
- case AST_T38_RATE_MANAGEMENT_LOCAL_TCF:
- a_line = ast_sdp_a_alloc("T38FaxRateManagement", "localTCF");
- if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
- ast_sdp_a_free(a_line);
- ast_sdp_m_free(m_line);
- return -1;
- }
- break;
- }
+ snprintf(tmp, sizeof(tmp), "%u", ast_udptl_get_local_max_datagram(udptl->instance));
+ a_line = ast_sdp_a_alloc("T38FaxMaxDatagram", tmp);
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
+ }
- snprintf(tmp, sizeof(tmp), "%u", ast_udptl_get_local_max_datagram(udptl->instance));
- a_line = ast_sdp_a_alloc("T38FaxMaxDatagram", tmp);
+ switch (ast_udptl_get_error_correction_scheme(udptl->instance)) {
+ case UDPTL_ERROR_CORRECTION_NONE:
+ break;
+ case UDPTL_ERROR_CORRECTION_FEC:
+ a_line = ast_sdp_a_alloc("T38FaxUdpEC", "t38UDPFEC");
if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
ast_sdp_a_free(a_line);
ast_sdp_m_free(m_line);
return -1;
}
-
- switch (ast_udptl_get_error_correction_scheme(udptl->instance)) {
- case UDPTL_ERROR_CORRECTION_NONE:
- break;
- case UDPTL_ERROR_CORRECTION_FEC:
- a_line = ast_sdp_a_alloc("T38FaxUdpEC", "t38UDPFEC");
- if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
- ast_sdp_a_free(a_line);
- ast_sdp_m_free(m_line);
- return -1;
- }
- break;
- case UDPTL_ERROR_CORRECTION_REDUNDANCY:
- a_line = ast_sdp_a_alloc("T38FaxUdpEC", "t38UDPRedundancy");
- if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
- ast_sdp_a_free(a_line);
- ast_sdp_m_free(m_line);
- return -1;
- }
- break;
+ break;
+ case UDPTL_ERROR_CORRECTION_REDUNDANCY:
+ a_line = ast_sdp_a_alloc("T38FaxUdpEC", "t38UDPRedundancy");
+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {
+ ast_sdp_a_free(a_line);
+ ast_sdp_m_free(m_line);
+ return -1;
}
+ break;
}
if (ast_sdp_add_m(sdp, m_line)) {
@@ -1749,7 +3333,7 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta
uint32_t t;
int stream_count;
- options = ast_sdp_state_get_options(sdp_state);
+ options = sdp_state->options;
topology = capabilities->topology;
t = tv.tv_sec + 2208988800UL;
@@ -1778,23 +3362,26 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta
}
stream_count = ast_stream_topology_get_count(topology);
-
for (stream_num = 0; stream_num < stream_count; stream_num++) {
switch (ast_stream_get_type(ast_stream_topology_get_stream(topology, stream_num))) {
case AST_MEDIA_TYPE_AUDIO:
case AST_MEDIA_TYPE_VIDEO:
- if (sdp_add_m_from_rtp_stream(sdp, sdp_state, options, capabilities, stream_num)) {
+ if (sdp_add_m_from_rtp_stream(sdp, sdp_state, capabilities, stream_num)) {
goto error;
}
break;
case AST_MEDIA_TYPE_IMAGE:
- if (sdp_add_m_from_udptl_stream(sdp, sdp_state, options, capabilities, stream_num)) {
+ if (sdp_add_m_from_udptl_stream(sdp, sdp_state, capabilities, stream_num)) {
goto error;
}
break;
case AST_MEDIA_TYPE_UNKNOWN:
case AST_MEDIA_TYPE_TEXT:
case AST_MEDIA_TYPE_END:
+ /* Decline any of these streams from the remote. */
+ if (sdp_add_m_from_declined_remote_stream(sdp, sdp_state, stream_num)) {
+ goto error;
+ }
break;
}
}
@@ -1803,7 +3390,7 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta
error:
if (sdp) {
- ast_sdp_free(sdp);
+ ao2_ref(sdp, -1);
} else {
ast_sdp_t_free(t_line);
ast_sdp_s_free(s_line);
diff --git a/main/stream.c b/main/stream.c
index 20179f331..89ed0dc53 100644
--- a/main/stream.c
+++ b/main/stream.c
@@ -214,6 +214,23 @@ const char *ast_stream_state2str(enum ast_stream_state state)
}
}
+enum ast_stream_state ast_stream_str2state(const char *str)
+{
+ if (!strcmp("sendrecv", str)) {
+ return AST_STREAM_STATE_SENDRECV;
+ }
+ if (!strcmp("sendonly", str)) {
+ return AST_STREAM_STATE_SENDONLY;
+ }
+ if (!strcmp("recvonly", str)) {
+ return AST_STREAM_STATE_RECVONLY;
+ }
+ if (!strcmp("inactive", str)) {
+ return AST_STREAM_STATE_INACTIVE;
+ }
+ return AST_STREAM_STATE_REMOVED;
+}
+
void *ast_stream_get_data(struct ast_stream *stream, enum ast_stream_data_slot slot)
{
ast_assert(stream != NULL);
@@ -284,6 +301,53 @@ struct ast_stream_topology *ast_stream_topology_clone(
return new_topology;
}
+int ast_stream_topology_equal(const struct ast_stream_topology *left,
+ const struct ast_stream_topology *right)
+{
+ int index;
+
+ ast_assert(left != NULL);
+ ast_assert(right != NULL);
+
+ if (ast_stream_topology_get_count(left) != ast_stream_topology_get_count(right)) {
+ return 0;
+ }
+
+ for (index = 0; index < ast_stream_topology_get_count(left); ++index) {
+ const struct ast_stream *left_stream = ast_stream_topology_get_stream(left, index);
+ const struct ast_stream *right_stream = ast_stream_topology_get_stream(right, index);
+
+ if (ast_stream_get_type(left_stream) != ast_stream_get_type(right_stream)) {
+ return 0;
+ }
+
+ if (ast_stream_get_state(left_stream) != ast_stream_get_state(right_stream)) {
+ return 0;
+ }
+
+ if (!ast_stream_get_formats(left_stream) && ast_stream_get_formats(right_stream) &&
+ ast_format_cap_count(ast_stream_get_formats(right_stream))) {
+ /* A NULL format capabilities and an empty format capabilities are the same, as they have
+ * no formats inside. If one does though... they are not equal.
+ */
+ return 0;
+ } else if (!ast_stream_get_formats(right_stream) && ast_stream_get_formats(left_stream) &&
+ ast_format_cap_count(ast_stream_get_formats(left_stream))) {
+ return 0;
+ } else if (ast_stream_get_formats(left_stream) && ast_stream_get_formats(right_stream) &&
+ !ast_format_cap_identical(ast_stream_get_formats(left_stream), ast_stream_get_formats(right_stream))) {
+ /* But if both are actually present we need to do an actual identical check. */
+ return 0;
+ }
+
+ if (strcmp(ast_stream_get_name(left_stream), ast_stream_get_name(right_stream))) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
void ast_stream_topology_free(struct ast_stream_topology *topology)
{
if (!topology) {
diff --git a/res/res_agi.c b/res/res_agi.c
index e8497f7ca..466063557 100644
--- a/res/res_agi.c
+++ b/res/res_agi.c
@@ -2393,7 +2393,7 @@ static int handle_waitfordigit(struct ast_channel *chan, AGI *agi, int argc, con
return RESULT_SHOWUSAGE;
if (sscanf(argv[3], "%30d", &to) != 1)
return RESULT_SHOWUSAGE;
- res = ast_waitfordigit_full(chan, to, agi->audio, agi->ctrl);
+ res = ast_waitfordigit_full(chan, to, NULL, agi->audio, agi->ctrl);
ast_agi_send(agi->fd, chan, "200 result=%d\n", res);
return (res >= 0) ? RESULT_SUCCESS : RESULT_FAILURE;
}
@@ -2673,7 +2673,7 @@ static int handle_getoption(struct ast_channel *chan, AGI *agi, int argc, const
/* If the user didnt press a key, wait for digitTimeout*/
if (res == 0 ) {
- res = ast_waitfordigit_full(chan, timeout, agi->audio, agi->ctrl);
+ res = ast_waitfordigit_full(chan, timeout, NULL, agi->audio, agi->ctrl);
/* Make sure the new result is in the escape digits of the GET OPTION */
if ( !strchr(edigits,res) )
res=0;
diff --git a/res/res_corosync.c b/res/res_corosync.c
index 79cd810ce..0d91b1860 100644
--- a/res/res_corosync.c
+++ b/res/res_corosync.c
@@ -77,6 +77,15 @@ struct corosync_node {
struct ast_sockaddr addr;
};
+/*! \brief Corosync ipc dispatch/request and reply size */
+#define COROSYNC_IPC_BUFFER_SIZE (8192 * 128)
+
+/*! \brief Version of pthread_create to ensure stack is large enough */
+#define corosync_pthread_create_background(a, b, c, d) \
+ ast_pthread_create_stack(a, b, c, d, \
+ (AST_BACKGROUND_STACKSIZE + (3 * COROSYNC_IPC_BUFFER_SIZE)), \
+ __FILE__, __FUNCTION__, __LINE__, #c)
+
static struct corosync_node *corosync_node_alloc(struct ast_event *event)
{
struct corosync_node *node;
@@ -808,10 +817,21 @@ static char *corosync_show_members(struct ast_cli_entry *e, int cmd, struct ast_
for (i = 1, cs_err = cpg_iteration_next(cpg_iter, &cpg_desc);
cs_err == CS_OK;
cs_err = cpg_iteration_next(cpg_iter, &cpg_desc), i++) {
+#ifdef HAVE_COROSYNC_CFG_STATE_TRACK
corosync_cfg_node_address_t addrs[8];
int num_addrs = 0;
unsigned int j;
+#endif
+
+ ast_cli(a->fd, "=== Node %u\n", i);
+ ast_cli(a->fd, "=== --> Group: %s\n", cpg_desc.group.value);
+#ifdef HAVE_COROSYNC_CFG_STATE_TRACK
+ /*
+ * Corosync 2.x cfg lib needs to allocate 1M on stack after calling
+ * corosync_cfg_get_node_addrs. netconsole thread has allocated only 0.5M
+ * resulting in crash.
+ */
cs_err = corosync_cfg_get_node_addrs(cfg_handle, cpg_desc.nodeid,
ARRAY_LEN(addrs), &num_addrs, addrs);
if (cs_err != CS_OK) {
@@ -819,9 +839,6 @@ static char *corosync_show_members(struct ast_cli_entry *e, int cmd, struct ast_
continue;
}
- ast_cli(a->fd, "=== Node %u\n", i);
- ast_cli(a->fd, "=== --> Group: %s\n", cpg_desc.group.value);
-
for (j = 0; j < num_addrs; j++) {
struct sockaddr *sa = (struct sockaddr *) addrs[j].address;
size_t sa_len = (size_t) addrs[j].address_length;
@@ -831,7 +848,9 @@ static char *corosync_show_members(struct ast_cli_entry *e, int cmd, struct ast_
ast_cli(a->fd, "=== --> Address %u: %s\n", j + 1, buf);
}
-
+#else
+ ast_cli(a->fd, "=== --> Nodeid: %"PRIu32"\n", cpg_desc.nodeid);
+#endif
}
ast_cli(a->fd, "===\n"
@@ -1157,7 +1176,7 @@ static int load_module(void)
goto failed;
}
- if (ast_pthread_create_background(&dispatch_thread.id, NULL,
+ if (corosync_pthread_create_background(&dispatch_thread.id, NULL,
dispatch_thread_handler, NULL)) {
ast_log(LOG_ERROR, "Error starting CPG dispatch thread.\n");
goto failed;
diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c
index be50e9cee..e4bb7a2d9 100644
--- a/res/res_musiconhold.c
+++ b/res/res_musiconhold.c
@@ -159,6 +159,11 @@ struct moh_files_state {
static struct ast_flags global_flags[1] = {{0}}; /*!< global MOH_ flags */
+enum kill_methods {
+ KILL_METHOD_PROCESS_GROUP = 0,
+ KILL_METHOD_PROCESS
+};
+
struct mohclass {
char name[MAX_MUSICCLASS];
char dir[256];
@@ -179,6 +184,10 @@ struct mohclass {
int pid;
time_t start;
pthread_t thread;
+ /*! Millisecond delay between kill attempts */
+ size_t kill_delay;
+ /*! Kill method */
+ enum kill_methods kill_method;
/*! Source of audio */
int srcfd;
/*! Generic timer */
@@ -680,6 +689,51 @@ static int spawn_mp3(struct mohclass *class)
return fds[0];
}
+static int killer(pid_t pid, int signum, enum kill_methods kill_method)
+{
+ switch (kill_method) {
+ case KILL_METHOD_PROCESS_GROUP:
+ return killpg(pid, signum);
+ case KILL_METHOD_PROCESS:
+ return kill(pid, signum);
+ }
+
+ return -1;
+}
+
+static void killpid(int pid, size_t delay, enum kill_methods kill_method)
+{
+ if (killer(pid, SIGHUP, kill_method) < 0) {
+ if (errno == ESRCH) {
+ return;
+ }
+ ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process '%d'?!!: %s\n", pid, strerror(errno));
+ } else {
+ ast_debug(1, "Sent HUP to pid %d%s\n", pid,
+ kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only");
+ }
+ usleep(delay);
+ if (killer(pid, SIGTERM, kill_method) < 0) {
+ if (errno == ESRCH) {
+ return;
+ }
+ ast_log(LOG_WARNING, "Unable to terminate MOH process '%d'?!!: %s\n", pid, strerror(errno));
+ } else {
+ ast_debug(1, "Sent TERM to pid %d%s\n", pid,
+ kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only");
+ }
+ usleep(delay);
+ if (killer(pid, SIGKILL, kill_method) < 0) {
+ if (errno == ESRCH) {
+ return;
+ }
+ ast_log(LOG_WARNING, "Unable to kill MOH process '%d'?!!: %s\n", pid, strerror(errno));
+ } else {
+ ast_debug(1, "Sent KILL to pid %d%s\n", pid,
+ kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only");
+ }
+}
+
static void *monmp3thread(void *data)
{
#define MOH_MS_INTERVAL 100
@@ -755,28 +809,7 @@ static void *monmp3thread(void *data)
class->srcfd = -1;
pthread_testcancel();
if (class->pid > 1) {
- do {
- if (killpg(class->pid, SIGHUP) < 0) {
- if (errno == ESRCH) {
- break;
- }
- ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno));
- }
- usleep(100000);
- if (killpg(class->pid, SIGTERM) < 0) {
- if (errno == ESRCH) {
- break;
- }
- ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno));
- }
- usleep(100000);
- if (killpg(class->pid, SIGKILL) < 0) {
- if (errno == ESRCH) {
- break;
- }
- ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno));
- }
- } while (0);
+ killpid(class->pid, class->kill_delay, class->kill_method);
class->pid = 0;
}
} else {
@@ -1357,6 +1390,7 @@ static struct mohclass *_moh_class_malloc(const char *file, int line, const char
if (class) {
class->format = ao2_bump(ast_format_slin);
class->srcfd = -1;
+ class->kill_delay = 100000;
}
return class;
@@ -1610,44 +1644,22 @@ static void moh_class_destructor(void *obj)
if (class->pid > 1) {
char buff[8192];
- int bytes, tbytes = 0, stime = 0, pid = 0;
+ int bytes, tbytes = 0, stime = 0;
ast_debug(1, "killing %d!\n", class->pid);
stime = time(NULL) + 2;
- pid = class->pid;
- class->pid = 0;
-
- /* Back when this was just mpg123, SIGKILL was fine. Now we need
- * to give the process a reason and time enough to kill off its
- * children. */
- do {
- if (killpg(pid, SIGHUP) < 0) {
- ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno));
- }
- usleep(100000);
- if (killpg(pid, SIGTERM) < 0) {
- if (errno == ESRCH) {
- break;
- }
- ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno));
- }
- usleep(100000);
- if (killpg(pid, SIGKILL) < 0) {
- if (errno == ESRCH) {
- break;
- }
- ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno));
- }
- } while (0);
+ killpid(class->pid, class->kill_delay, class->kill_method);
while ((ast_wait_for_input(class->srcfd, 100) > 0) &&
(bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) {
tbytes = tbytes + bytes;
}
- ast_debug(1, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes);
+ ast_debug(1, "mpg123 pid %d and child died after %d bytes read\n",
+ class->pid, tbytes);
+ class->pid = 0;
close(class->srcfd);
class->srcfd = -1;
}
@@ -1752,6 +1764,49 @@ static int load_moh_classes(int reload)
/* For compatibility with the past, we overwrite any name=name
* with the context [name]. */
ast_copy_string(class->name, cat, sizeof(class->name));
+ for (var = ast_variable_browse(cfg, cat); var; var = var->next) {
+ if (!strcasecmp(var->name, "mode")) {
+ ast_copy_string(class->mode, var->value, sizeof(class->mode));
+ } else if (!strcasecmp(var->name, "directory")) {
+ ast_copy_string(class->dir, var->value, sizeof(class->dir));
+ } else if (!strcasecmp(var->name, "application")) {
+ ast_copy_string(class->args, var->value, sizeof(class->args));
+ } else if (!strcasecmp(var->name, "announcement")) {
+ ast_copy_string(class->announcement, var->value, sizeof(class->announcement));
+ ast_set_flag(class, MOH_ANNOUNCEMENT);
+ } else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value))) {
+ class->digit = *var->value;
+ } else if (!strcasecmp(var->name, "random")) {
+ ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE);
+ } else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random")) {
+ ast_set_flag(class, MOH_RANDOMIZE);
+ } else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha")) {
+ ast_set_flag(class, MOH_SORTALPHA);
+ } else if (!strcasecmp(var->name, "format")) {
+ ao2_cleanup(class->format);
+ class->format = ast_format_cache_get(var->value);
+ if (!class->format) {
+ ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value);
+ class->format = ao2_bump(ast_format_slin);
+ }
+ } else if (!strcasecmp(var->name, "kill_escalation_delay")) {
+ if (sscanf(var->value, "%zu", &class->kill_delay) == 1) {
+ class->kill_delay *= 1000;
+ } else {
+ ast_log(LOG_WARNING, "kill_escalation_delay '%s' is invalid. Setting to 100ms\n", var->value);
+ class->kill_delay = 100000;
+ }
+ } else if (!strcasecmp(var->name, "kill_method")) {
+ if (!strcasecmp(var->value, "process")) {
+ class->kill_method = KILL_METHOD_PROCESS;
+ } else if (!strcasecmp(var->value, "process_group")){
+ class->kill_method = KILL_METHOD_PROCESS_GROUP;
+ } else {
+ ast_log(LOG_WARNING, "kill_method '%s' is invalid. Setting to 'process_group'\n", var->value);
+ class->kill_method = KILL_METHOD_PROCESS_GROUP;
+ }
+ }
+ }
if (ast_strlen_zero(class->dir)) {
if (!strcasecmp(class->mode, "custom")) {
@@ -1884,6 +1939,9 @@ static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struc
ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "<none>"));
if (ast_test_flag(class, MOH_CUSTOM)) {
ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
+ ast_cli(a->fd, "\tKill Escalation Delay: %zu ms\n", class->kill_delay / 1000);
+ ast_cli(a->fd, "\tKill Method: %s\n",
+ class->kill_method == KILL_METHOD_PROCESS ? "process" : "process_group");
}
if (strcasecmp(class->mode, "files")) {
ast_cli(a->fd, "\tFormat: %s\n", ast_format_get_name(class->format));
diff --git a/res/res_odbc.c b/res/res_odbc.c
index 0b81bc639..24f63a92e 100644
--- a/res/res_odbc.c
+++ b/res/res_odbc.c
@@ -61,7 +61,6 @@
#include "asterisk/app.h"
#include "asterisk/strings.h"
#include "asterisk/threadstorage.h"
-#include "asterisk/data.h"
struct odbc_class
{
@@ -119,15 +118,6 @@ struct odbc_txn_frame {
char name[0]; /*!< Name of this transaction ID */
};
-#define DATA_EXPORT_ODBC_CLASS(MEMBER) \
- MEMBER(odbc_class, name, AST_DATA_STRING) \
- MEMBER(odbc_class, dsn, AST_DATA_STRING) \
- MEMBER(odbc_class, username, AST_DATA_STRING) \
- MEMBER(odbc_class, password, AST_DATA_PASSWORD) \
- MEMBER(odbc_class, forcecommit, AST_DATA_BOOLEAN)
-
-AST_DATA_STRUCTURE(odbc_class, DATA_EXPORT_ODBC_CLASS);
-
const char *ast_odbc_isolation2text(int iso)
{
if (iso == SQL_TXN_READ_COMMITTED) {
@@ -971,65 +961,6 @@ static odbc_status odbc_obj_connect(struct odbc_obj *obj)
return ODBC_SUCCESS;
}
-/*!
- * \internal
- * \brief Implements the channels provider.
- */
-static int data_odbc_provider_handler(const struct ast_data_search *search,
- struct ast_data *root)
-{
- struct ao2_iterator aoi;
- struct odbc_class *class;
- struct ast_data *data_odbc_class, *data_odbc_connections;
- struct ast_data *enum_node;
-
- aoi = ao2_iterator_init(class_container, 0);
- while ((class = ao2_iterator_next(&aoi))) {
- data_odbc_class = ast_data_add_node(root, "class");
- if (!data_odbc_class) {
- ao2_ref(class, -1);
- continue;
- }
-
- ast_data_add_structure(odbc_class, data_odbc_class, class);
-
- data_odbc_connections = ast_data_add_node(data_odbc_class, "connections");
- if (!data_odbc_connections) {
- ao2_ref(class, -1);
- continue;
- }
-
- /* isolation */
- enum_node = ast_data_add_node(data_odbc_class, "isolation");
- if (!enum_node) {
- ao2_ref(class, -1);
- continue;
- }
- ast_data_add_int(enum_node, "value", class->isolation);
- ast_data_add_str(enum_node, "text", ast_odbc_isolation2text(class->isolation));
- ao2_ref(class, -1);
-
- if (!ast_data_search_match(search, data_odbc_class)) {
- ast_data_remove_node(root, data_odbc_class);
- }
- }
- ao2_iterator_destroy(&aoi);
- return 0;
-}
-
-/*!
- * \internal
- * \brief /asterisk/res/odbc/listprovider.
- */
-static const struct ast_data_handler odbc_provider = {
- .version = AST_DATA_HANDLER_VERSION,
- .get = data_odbc_provider_handler
-};
-
-static const struct ast_data_entry odbc_providers[] = {
- AST_DATA_ENTRY("/asterisk/res/odbc", &odbc_provider),
-};
-
static int reload(void)
{
struct odbc_cache_tables *table;
@@ -1087,7 +1018,6 @@ static int load_module(void)
if (load_odbc_config() == -1)
return AST_MODULE_LOAD_DECLINE;
ast_cli_register_multiple(cli_odbc, ARRAY_LEN(cli_odbc));
- ast_data_register_multiple(odbc_providers, ARRAY_LEN(odbc_providers));
ast_log(LOG_NOTICE, "res_odbc loaded.\n");
return 0;
}
diff --git a/res/res_pjsip.c b/res/res_pjsip.c
index d994f2824..02112113c 100644
--- a/res/res_pjsip.c
+++ b/res/res_pjsip.c
@@ -193,11 +193,18 @@
<description>
<para>Method used when updating connected line information.</para>
<enumlist>
- <enum name="invite" />
+ <enum name="invite">
+ <para>When set to <literal>invite</literal>, check the remote's Allow header and
+ if UPDATE is allowed, send UPDATE instead of INVITE to avoid SDP
+ renegotiation. If UPDATE is not Allowed, send INVITE.</para>
+ </enum>
<enum name="reinvite">
<para>Alias for the <literal>invite</literal> value.</para>
</enum>
- <enum name="update" />
+ <enum name="update">
+ <para>If set to <literal>update</literal>, send UPDATE regardless of what the remote
+ Allows. </para>
+ </enum>
</enumlist>
</description>
</configOption>
@@ -229,6 +236,9 @@
<enum name="auto">
<para>DTMF is sent as RFC 4733 if the other side supports it or as INBAND if not.</para>
</enum>
+ <enum name="auto_info">
+ <para>DTMF is sent as RFC 4733 if the other side supports it or as SIP INFO if not.</para>
+ </enum>
</enumlist>
</description>
</configOption>
@@ -971,6 +981,47 @@
will not send the progress details, but immediately will send "200 OK".
</para></description>
</configOption>
+ <configOption name="notify_early_inuse_ringing" default="no">
+ <synopsis>Whether to notifies dialog-info 'early' on InUse&amp;Ringing state</synopsis>
+ <description><para>
+ Control whether dialog-info subscriptions get 'early' state
+ on Ringing when already INUSE.
+ </para></description>
+ </configOption>
+ <configOption name="max_audio_streams" default="1">
+ <synopsis>The maximum number of allowed audio streams for the endpoint</synopsis>
+ <description><para>
+ This option enforces a limit on the maximum simultaneous negotiated audio
+ streams allowed for the endpoint.
+ </para></description>
+ </configOption>
+ <configOption name="max_video_streams" default="1">
+ <synopsis>The maximum number of allowed video streams for the endpoint</synopsis>
+ <description><para>
+ This option enforces a limit on the maximum simultaneous negotiated video
+ streams allowed for the endpoint.
+ </para></description>
+ </configOption>
+ <configOption name="bundle" default="no">
+ <synopsis>Enable RTP bundling</synopsis>
+ <description><para>
+ With this option enabled, Asterisk will attempt to negotiate the use of bundle.
+ If negotiated this will result in multiple RTP streams being carried over the same
+ 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>
@@ -3060,6 +3111,14 @@ pjsip_dialog *ast_sip_create_dialog_uac(const struct ast_sip_endpoint *endpoint,
/* Update the dialog with the new local URI, we do it afterwards so we can use the dialog pool for construction */
pj_strdup_with_null(dlg->pool, &dlg->local.info_str, &local_uri);
dlg->local.info->uri = pjsip_parse_uri(dlg->pool, dlg->local.info_str.ptr, dlg->local.info_str.slen, 0);
+ if (!dlg->local.info->uri) {
+ ast_log(LOG_ERROR,
+ "Could not parse URI '%s' for endpoint '%s'\n",
+ dlg->local.info_str.ptr, ast_sorcery_object_get_id(endpoint));
+ dlg->sess_count--;
+ pjsip_dlg_terminate(dlg);
+ return NULL;
+ }
dlg->local.contact = pjsip_parse_hdr(dlg->pool, &HCONTACT, local_uri.ptr, local_uri.slen, NULL);
@@ -4197,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;
diff --git a/res/res_pjsip.exports.in b/res/res_pjsip.exports.in
index 8b62abbfe..4adecd419 100644
--- a/res/res_pjsip.exports.in
+++ b/res/res_pjsip.exports.in
@@ -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:
*;
diff --git a/res/res_pjsip/pjsip_configuration.c b/res/res_pjsip/pjsip_configuration.c
index 679c8837d..9f9de36fa 100644
--- a/res/res_pjsip/pjsip_configuration.c
+++ b/res/res_pjsip/pjsip_configuration.c
@@ -22,6 +22,7 @@
#include "asterisk/test.h"
#include "asterisk/statsd.h"
#include "asterisk/pbx.h"
+#include "asterisk/stream.h"
/*! \brief Number of buckets for persistent endpoint information */
#define PERSISTENT_BUCKETS 53
@@ -370,6 +371,8 @@ static int dtmf_handler(const struct aco_option *opt, struct ast_variable *var,
endpoint->dtmf = AST_SIP_DTMF_RFC_4733;
} else if (!strcasecmp(var->value, "inband")) {
endpoint->dtmf = AST_SIP_DTMF_INBAND;
+ } else if (!strcasecmp(var->value, "auto_info")) {
+ endpoint->dtmf = AST_SIP_DTMF_AUTO_INFO;
} else if (!strcasecmp(var->value, "info")) {
endpoint->dtmf = AST_SIP_DTMF_INFO;
} else if (!strcasecmp(var->value, "auto")) {
@@ -394,8 +397,11 @@ static int dtmf_to_str(const void *obj, const intptr_t *args, char **buf)
*buf = "inband"; break;
case AST_SIP_DTMF_INFO :
*buf = "info"; break;
- case AST_SIP_DTMF_AUTO :
+ case AST_SIP_DTMF_AUTO :
*buf = "auto"; break;
+ case AST_SIP_DTMF_AUTO_INFO :
+ *buf = "auto_info";
+ break;
default:
*buf = "none";
}
@@ -1142,6 +1148,37 @@ static int tos_video_to_str(const void *obj, const intptr_t *args, char **buf)
return 0;
}
+static int from_user_handler(const struct aco_option *opt,
+ struct ast_variable *var, void *obj)
+{
+ struct ast_sip_endpoint *endpoint = obj;
+ /* Valid non-alphanumeric characters for URI */
+ char *valid_uri_marks = "-_.!~*`()";
+ const char *val;
+
+ for (val = var->value; *val; val++) {
+ if (!strchr(valid_uri_marks, *val) && !isdigit(*val) && !isalpha(*val)) {
+ ast_log(LOG_ERROR, "Error configuring endpoint '%s' - '%s' field "
+ "contains invalid character '%c'\n",
+ ast_sorcery_object_get_id(endpoint), var->name, *val);
+ return -1;
+ }
+ }
+
+ ast_string_field_set(endpoint, fromuser, var->value);
+
+ return 0;
+}
+
+static int from_user_to_str(const void *obj, const intptr_t *args, char **buf)
+{
+ const struct ast_sip_endpoint *endpoint = obj;
+
+ *buf = ast_strdup(endpoint->fromuser);
+
+ return 0;
+}
+
static int set_var_handler(const struct aco_option *opt,
struct ast_variable *var, void *obj)
{
@@ -1321,6 +1358,37 @@ static int sip_endpoint_apply_handler(const struct ast_sorcery *sorcery, void *o
return -1;
}
+ endpoint->media.topology = ast_stream_topology_create_from_format_cap(endpoint->media.codecs);
+ if (!endpoint->media.topology) {
+ return -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;
}
@@ -1907,7 +1975,7 @@ int ast_res_pjsip_initialize_configuration(void)
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "cos_video", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, media.cos_video));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_subscribe", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.allow));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "sub_min_expiry", "0", OPT_UINT_T, 0, FLDSET(struct ast_sip_endpoint, subscription.minexpiry));
- ast_sorcery_object_field_register(sip_sorcery, "endpoint", "from_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, fromuser));
+ ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "from_user", "", from_user_handler, from_user_to_str, NULL, 0, 0);
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "from_domain", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, fromdomain));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mwi_from_user", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.fromuser));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtp_engine", "asterisk", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, media.rtp.engine));
@@ -1940,6 +2008,11 @@ int ast_res_pjsip_initialize_configuration(void)
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rtcp_mux", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, media.rtcp_mux));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "allow_overlap", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, allow_overlap));
ast_sorcery_object_field_register(sip_sorcery, "endpoint", "refer_blind_progress", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, refer_blind_progress));
+ ast_sorcery_object_field_register(sip_sorcery, "endpoint", "notify_early_inuse_ringing", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, notify_early_inuse_ringing));
+ 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");
@@ -2059,7 +2132,8 @@ static void endpoint_destructor(void* obj)
ast_string_field_free_memory(endpoint);
- ao2_ref(endpoint->media.codecs, -1);
+ ao2_cleanup(endpoint->media.codecs);
+ ast_stream_topology_free(endpoint->media.topology);
subscription_configuration_destroy(&endpoint->subscription);
info_configuration_destroy(&endpoint->info);
media_configuration_destroy(&endpoint->media);
diff --git a/res/res_pjsip/pjsip_distributor.c b/res/res_pjsip/pjsip_distributor.c
index cca26a83c..cf1b04a8b 100644
--- a/res/res_pjsip/pjsip_distributor.c
+++ b/res/res_pjsip/pjsip_distributor.c
@@ -150,62 +150,189 @@ static struct ast_taskprocessor *find_request_serializer(pjsip_rx_data *rdata)
/*! Dialog-specific information the distributor uses */
struct distributor_dialog_data {
+ /*! dialog_associations ao2 container key */
+ pjsip_dialog *dlg;
/*! Serializer to distribute tasks to for this dialog */
struct ast_taskprocessor *serializer;
/*! Endpoint associated with this dialog */
struct ast_sip_endpoint *endpoint;
};
+#define DIALOG_ASSOCIATIONS_BUCKETS 251
+
+static struct ao2_container *dialog_associations;
+
/*!
* \internal
+ * \brief Compute a hash value on an arbitrary buffer.
+ * \since 13.17.0
*
- * \note Call this with the dialog locked
+ * \param[in] pos The buffer to add to the hash
+ * \param[in] len The buffer length to add to the hash
+ * \param[in] hash The hash value to add to
+ *
+ * \details
+ * This version of the function is for when you need to compute a
+ * hash of more than one buffer.
+ *
+ * This famous hash algorithm was written by Dan Bernstein and is
+ * commonly used.
+ *
+ * \sa http://www.cse.yorku.ca/~oz/hash.html
*/
-static struct distributor_dialog_data *distributor_dialog_data_alloc(pjsip_dialog *dlg)
+static int buf_hash_add(const char *pos, size_t len, int hash)
{
- struct distributor_dialog_data *dist;
+ while (len--) {
+ hash = hash * 33 ^ *pos++;
+ }
+
+ return hash;
+}
+
+/*!
+ * \internal
+ * \brief Compute a hash value on an arbitrary buffer.
+ * \since 13.17.0
+ *
+ * \param[in] pos The buffer to add to the hash
+ * \param[in] len The buffer length to add to the hash
+ *
+ * \details
+ * This version of the function is for when you need to compute a
+ * hash of more than one buffer.
+ *
+ * This famous hash algorithm was written by Dan Bernstein and is
+ * commonly used.
+ *
+ * \sa http://www.cse.yorku.ca/~oz/hash.html
+ */
+static int buf_hash(const char *pos, size_t len)
+{
+ return buf_hash_add(pos, len, 5381);
+}
- dist = PJ_POOL_ZALLOC_T(dlg->pool, struct distributor_dialog_data);
- pjsip_dlg_set_mod_data(dlg, distributor_mod.id, dist);
+static int dialog_associations_hash(const void *obj, int flags)
+{
+ const struct distributor_dialog_data *object;
+ union {
+ const pjsip_dialog *dlg;
+ const char buf[sizeof(pjsip_dialog *)];
+ } key;
- return dist;
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_KEY:
+ key.dlg = obj;
+ break;
+ case OBJ_SEARCH_OBJECT:
+ object = obj;
+ key.dlg = object->dlg;
+ break;
+ default:
+ /* Hash can only work on something with a full key. */
+ ast_assert(0);
+ return 0;
+ }
+ return ast_str_hash_restrict(buf_hash(key.buf, sizeof(key.buf)));
+}
+
+static int dialog_associations_cmp(void *obj, void *arg, int flags)
+{
+ const struct distributor_dialog_data *object_left = obj;
+ const struct distributor_dialog_data *object_right = arg;
+ const pjsip_dialog *right_key = arg;
+ int cmp = 0;
+
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_OBJECT:
+ right_key = object_right->dlg;
+ /* Fall through */
+ case OBJ_SEARCH_KEY:
+ if (object_left->dlg == right_key) {
+ cmp = CMP_MATCH;
+ }
+ break;
+ case OBJ_SEARCH_PARTIAL_KEY:
+ /* There is no such thing for this container. */
+ ast_assert(0);
+ break;
+ default:
+ cmp = 0;
+ break;
+ }
+ return cmp;
}
void ast_sip_dialog_set_serializer(pjsip_dialog *dlg, struct ast_taskprocessor *serializer)
{
struct distributor_dialog_data *dist;
- SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock);
- dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id);
+ ao2_wrlock(dialog_associations);
+ dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (!dist) {
- dist = distributor_dialog_data_alloc(dlg);
+ if (serializer) {
+ dist = ao2_alloc(sizeof(*dist), NULL);
+ if (dist) {
+ dist->dlg = dlg;
+ dist->serializer = serializer;
+ ao2_link_flags(dialog_associations, dist, OBJ_NOLOCK);
+ ao2_ref(dist, -1);
+ }
+ }
+ } else {
+ ao2_lock(dist);
+ dist->serializer = serializer;
+ if (!dist->serializer && !dist->endpoint) {
+ ao2_unlink_flags(dialog_associations, dist, OBJ_NOLOCK);
+ }
+ ao2_unlock(dist);
+ ao2_ref(dist, -1);
}
- dist->serializer = serializer;
+ ao2_unlock(dialog_associations);
}
void ast_sip_dialog_set_endpoint(pjsip_dialog *dlg, struct ast_sip_endpoint *endpoint)
{
struct distributor_dialog_data *dist;
- SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock);
- dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id);
+ ao2_wrlock(dialog_associations);
+ dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY | OBJ_NOLOCK);
if (!dist) {
- dist = distributor_dialog_data_alloc(dlg);
+ if (endpoint) {
+ dist = ao2_alloc(sizeof(*dist), NULL);
+ if (dist) {
+ dist->dlg = dlg;
+ dist->endpoint = endpoint;
+ ao2_link_flags(dialog_associations, dist, OBJ_NOLOCK);
+ ao2_ref(dist, -1);
+ }
+ }
+ } else {
+ ao2_lock(dist);
+ dist->endpoint = endpoint;
+ if (!dist->serializer && !dist->endpoint) {
+ ao2_unlink_flags(dialog_associations, dist, OBJ_NOLOCK);
+ }
+ ao2_unlock(dist);
+ ao2_ref(dist, -1);
}
- dist->endpoint = endpoint;
+ ao2_unlock(dialog_associations);
}
struct ast_sip_endpoint *ast_sip_dialog_get_endpoint(pjsip_dialog *dlg)
{
struct distributor_dialog_data *dist;
- SCOPED_LOCK(lock, dlg, pjsip_dlg_inc_lock, pjsip_dlg_dec_lock);
+ struct ast_sip_endpoint *endpoint;
- dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id);
- if (!dist || !dist->endpoint) {
- return NULL;
+ dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY);
+ if (dist) {
+ ao2_lock(dist);
+ endpoint = ao2_bump(dist->endpoint);
+ ao2_unlock(dist);
+ ao2_ref(dist, -1);
+ } else {
+ endpoint = NULL;
}
- ao2_ref(dist->endpoint, +1);
- return dist->endpoint;
+ return endpoint;
}
static pjsip_dialog *find_dialog(pjsip_rx_data *rdata)
@@ -237,7 +364,7 @@ static pjsip_dialog *find_dialog(pjsip_rx_data *rdata)
pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, &pjsip_cancel_method) ||
rdata->msg_info.to->tag.slen != 0) {
dlg = pjsip_ua_find_dialog(&rdata->msg_info.cid->id, local_tag,
- remote_tag, PJ_TRUE);
+ remote_tag, PJ_FALSE);
if (dlg) {
return dlg;
}
@@ -275,11 +402,6 @@ static pjsip_dialog *find_dialog(pjsip_rx_data *rdata)
pj_mutex_unlock(tsx->mutex);
#endif
- if (!dlg) {
- return NULL;
- }
-
- pjsip_dlg_inc_lock(dlg);
return dlg;
}
@@ -302,16 +424,7 @@ static pjsip_dialog *find_dialog(pjsip_rx_data *rdata)
*/
static int pjstr_hash_add(pj_str_t *str, int hash)
{
- size_t len;
- const char *pos;
-
- len = pj_strlen(str);
- pos = pj_strbuf(str);
- while (len--) {
- hash = hash * 33 ^ *pos++;
- }
-
- return hash;
+ return buf_hash_add(pj_strbuf(str), pj_strlen(str), hash);
}
/*!
@@ -350,7 +463,7 @@ struct ast_taskprocessor *ast_sip_get_distributor_serializer(pjsip_rx_data *rdat
/* Compute the hash from the SIP message call-id and remote-tag */
hash = pjstr_hash(&rdata->msg_info.cid->id);
hash = pjstr_hash_add(remote_tag, hash);
- hash = abs(hash);
+ hash = ast_str_hash_restrict(hash);
serializer = ao2_bump(distributor_pool[hash % ARRAY_LEN(distributor_pool)]);
if (serializer) {
@@ -385,17 +498,18 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
dlg = find_dialog(rdata);
if (dlg) {
- ast_debug(3, "Searching for serializer on dialog %s for %s\n",
+ ast_debug(3, "Searching for serializer associated with dialog %s for %s\n",
dlg->obj_name, pjsip_rx_data_get_info(rdata));
- dist = pjsip_dlg_get_mod_data(dlg, distributor_mod.id);
+ dist = ao2_find(dialog_associations, dlg, OBJ_SEARCH_KEY);
if (dist) {
+ ao2_lock(dist);
serializer = ao2_bump(dist->serializer);
+ ao2_unlock(dist);
if (serializer) {
- ast_debug(3, "Found serializer %s on dialog %s\n",
+ ast_debug(3, "Found serializer %s associated with dialog %s\n",
ast_taskprocessor_name(serializer), dlg->obj_name);
}
}
- pjsip_dlg_dec_lock(dlg);
}
if (serializer) {
@@ -417,6 +531,7 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
/* We have a BYE or CANCEL request without a serializer. */
pjsip_endpt_respond_stateless(ast_sip_get_pjsip_endpoint(), rdata,
PJSIP_SC_CALL_TSX_DOES_NOT_EXIST, NULL, NULL, NULL);
+ ao2_cleanup(dist);
return PJ_TRUE;
} else {
if (ast_taskprocessor_alert_get()) {
@@ -431,6 +546,7 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
*/
ast_debug(3, "Taskprocessor overload alert: Ignoring '%s'.\n",
pjsip_rx_data_get_info(rdata));
+ ao2_cleanup(dist);
return PJ_TRUE;
}
@@ -438,10 +554,17 @@ static pj_bool_t distributor(pjsip_rx_data *rdata)
serializer = ast_sip_get_distributor_serializer(rdata);
}
- pjsip_rx_data_clone(rdata, 0, &clone);
+ if (pjsip_rx_data_clone(rdata, 0, &clone) != PJ_SUCCESS) {
+ ast_taskprocessor_unreference(serializer);
+ ao2_cleanup(dist);
+ return PJ_TRUE;
+ }
if (dist) {
+ ao2_lock(dist);
clone->endpt_info.mod_data[endpoint_mod.id] = ao2_bump(dist->endpoint);
+ ao2_unlock(dist);
+ ao2_cleanup(dist);
}
if (ast_sip_push_task(serializer, distribute, clone)) {
@@ -837,7 +960,7 @@ static int suspects_compare(void *obj, void *arg, int flags)
/* Fall through */
case OBJ_SEARCH_KEY:
if (strcmp(object_left->src_name, right_key) == 0) {
- cmp = CMP_MATCH | CMP_STOP;
+ cmp = CMP_MATCH;
}
break;
case OBJ_SEARCH_PARTIAL_KEY:
@@ -852,15 +975,25 @@ static int suspects_compare(void *obj, void *arg, int flags)
return cmp;
}
-static int suspects_hash(const void *obj, int flags) {
- const struct unidentified_request *object_left = obj;
+static int suspects_hash(const void *obj, int flags)
+{
+ const struct unidentified_request *object;
+ const char *key;
- if (flags & OBJ_SEARCH_OBJECT) {
- return ast_str_hash(object_left->src_name);
- } else if (flags & OBJ_SEARCH_KEY) {
- return ast_str_hash(obj);
+ switch (flags & OBJ_SEARCH_MASK) {
+ case OBJ_SEARCH_KEY:
+ key = obj;
+ break;
+ case OBJ_SEARCH_OBJECT:
+ object = obj;
+ key = object->src_name;
+ break;
+ default:
+ /* Hash can only work on something with a full key. */
+ ast_assert(0);
+ return 0;
}
- return -1;
+ return ast_str_hash(key);
}
static struct ao2_container *cli_unid_get_container(const char *regex)
@@ -1078,6 +1211,14 @@ int ast_sip_initialize_distributor(void)
return -1;
}
+ dialog_associations = ao2_container_alloc_hash(AO2_ALLOC_OPT_LOCK_RWLOCK, 0,
+ DIALOG_ASSOCIATIONS_BUCKETS, dialog_associations_hash, NULL,
+ dialog_associations_cmp);
+ if (!dialog_associations) {
+ ast_sip_destroy_distributor();
+ return -1;
+ }
+
if (distributor_pool_setup()) {
ast_sip_destroy_distributor();
return -1;
@@ -1156,5 +1297,6 @@ void ast_sip_destroy_distributor(void)
distributor_pool_shutdown();
+ ao2_cleanup(dialog_associations);
ao2_cleanup(unidentified_requests);
}
diff --git a/res/res_pjsip/presence_xml.c b/res/res_pjsip/presence_xml.c
index c991a0d68..1aca307e5 100644
--- a/res/res_pjsip/presence_xml.c
+++ b/res/res_pjsip/presence_xml.c
@@ -82,7 +82,8 @@ void ast_sip_sanitize_xml(const char *input, char *output, size_t len)
}
void ast_sip_presence_exten_state_to_str(int state, char **statestring, char **pidfstate,
- char **pidfnote, enum ast_sip_pidf_state *local_state)
+ char **pidfnote, enum ast_sip_pidf_state *local_state,
+ unsigned int notify_early_inuse_ringing)
{
switch (state) {
case AST_EXTENSION_RINGING:
@@ -92,7 +93,11 @@ void ast_sip_presence_exten_state_to_str(int state, char **statestring, char **p
*pidfnote = "Ringing";
break;
case (AST_EXTENSION_INUSE | AST_EXTENSION_RINGING):
- *statestring = "confirmed";
+ if (notify_early_inuse_ringing) {
+ *statestring = "early";
+ } else {
+ *statestring = "confirmed";
+ }
*local_state = NOTIFY_INUSE;
*pidfstate = "busy";
*pidfnote = "Ringing";
diff --git a/res/res_pjsip_dialog_info_body_generator.c b/res/res_pjsip_dialog_info_body_generator.c
index 5006b9efb..7c386e3b2 100644
--- a/res/res_pjsip_dialog_info_body_generator.c
+++ b/res/res_pjsip_dialog_info_body_generator.c
@@ -107,6 +107,8 @@ static int dialog_info_generate_body_content(void *body, void *data)
enum ast_sip_pidf_state local_state;
unsigned int version;
char version_str[32], sanitized[PJSIP_MAX_URL_SIZE];
+ struct ast_sip_endpoint *endpoint = NULL;
+ unsigned int notify_early_inuse_ringing = 0;
if (!local || !state_data->datastores) {
return -1;
@@ -120,8 +122,12 @@ static int dialog_info_generate_body_content(void *body, void *data)
stripped = ast_strip_quoted(local, "<", ">");
ast_sip_sanitize_xml(stripped, sanitized, sizeof(sanitized));
+ if (state_data->sub && (endpoint = ast_sip_subscription_get_endpoint(state_data->sub))) {
+ notify_early_inuse_ringing = endpoint->notify_early_inuse_ringing;
+ ao2_cleanup(endpoint);
+ }
ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
- &pidfstate, &pidfnote, &local_state);
+ &pidfstate, &pidfnote, &local_state, notify_early_inuse_ringing);
ast_sip_presence_xml_create_attr(state_data->pool, dialog_info, "xmlns", "urn:ietf:params:xml:ns:dialog-info");
@@ -133,7 +139,7 @@ static int dialog_info_generate_body_content(void *body, void *data)
dialog = ast_sip_presence_xml_create_node(state_data->pool, dialog_info, "dialog");
ast_sip_presence_xml_create_attr(state_data->pool, dialog, "id", state_data->exten);
- if (state_data->exten_state == AST_EXTENSION_RINGING) {
+ if (!ast_strlen_zero(statestring) && !strcmp(statestring, "early")) {
ast_sip_presence_xml_create_attr(state_data->pool, dialog, "direction", "recipient");
}
diff --git a/res/res_pjsip_mwi.c b/res/res_pjsip_mwi.c
index 3dfccef86..5423038e6 100644
--- a/res/res_pjsip_mwi.c
+++ b/res/res_pjsip_mwi.c
@@ -1102,6 +1102,13 @@ static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags
}
if (endpoint->subscription.mwi.aggregate) {
+ const char *endpoint_id = ast_sorcery_object_get_id(endpoint);
+
+ /* Check if subscription exists */
+ aggregate_sub = ao2_find(unsolicited_mwi, endpoint_id, OBJ_SEARCH_KEY | OBJ_NOLOCK);
+ if (aggregate_sub) {
+ return 0;
+ }
aggregate_sub = mwi_subscription_alloc(endpoint, 0, NULL);
if (!aggregate_sub) {
return 0;
@@ -1113,7 +1120,9 @@ static int create_mwi_subscriptions_for_endpoint(void *obj, void *arg, int flags
struct mwi_subscription *sub;
struct mwi_stasis_subscription *mwi_stasis_sub;
- if (ast_strlen_zero(mailbox)) {
+ /* check if subscription exists */
+ if (ast_strlen_zero(mailbox) ||
+ (!aggregate_sub && endpoint_receives_unsolicited_mwi_for_mailbox(endpoint, mailbox))) {
continue;
}
@@ -1189,31 +1198,79 @@ static int send_contact_notify(void *obj, void *arg, int flags)
return 0;
}
-/*! \brief Function called when a contact is updated */
-static void mwi_contact_updated(const void *object)
+/*! \brief Create mwi subscriptions and notify */
+static void mwi_contact_changed(const struct ast_sip_contact *contact)
{
- char *id = ast_strdupa(ast_sorcery_object_get_id(object)), *aor = NULL;
+ char *id = ast_strdupa(ast_sorcery_object_get_id(contact));
+ char *aor = NULL;
+ struct ast_sip_endpoint *endpoint = NULL;
- aor = strsep(&id, ";@");
+ if (contact->endpoint) {
+ endpoint = ao2_bump(contact->endpoint);
+ } else {
+ if (!ast_strlen_zero(contact->endpoint_name)) {
+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", contact->endpoint_name);
+ }
+ }
+ if (!endpoint || ast_strlen_zero(endpoint->subscription.mwi.mailboxes)) {
+ ao2_cleanup(endpoint);
+ return;
+ }
+
+ ao2_lock(unsolicited_mwi);
+ create_mwi_subscriptions_for_endpoint(endpoint, NULL, 0);
+ ao2_unlock(unsolicited_mwi);
+ ao2_cleanup(endpoint);
+
+ aor = strsep(&id, ";@");
ao2_callback(unsolicited_mwi, OBJ_NODATA, send_contact_notify, aor);
}
+/*! \brief Function called when a contact is updated */
+static void mwi_contact_updated(const void *object)
+{
+ mwi_contact_changed(object);
+}
+
/*! \brief Function called when a contact is added */
static void mwi_contact_added(const void *object)
{
+ mwi_contact_changed(object);
+}
+
+/*! \brief Function called when a contact is deleted */
+static void mwi_contact_deleted(const void *object)
+{
const struct ast_sip_contact *contact = object;
struct ao2_iterator *mwi_subs;
struct mwi_subscription *mwi_sub;
- const char *endpoint_id = ast_sorcery_object_get_id(contact->endpoint);
+ struct ast_sip_endpoint *endpoint = NULL;
+ struct ast_sip_contact *found_contact;
+
+ if (contact->endpoint) {
+ endpoint = ao2_bump(contact->endpoint);
+ } else {
+ if (!ast_strlen_zero(contact->endpoint_name)) {
+ endpoint = ast_sorcery_retrieve_by_id(ast_sip_get_sorcery(), "endpoint", contact->endpoint_name);
+ }
+ }
- if (ast_strlen_zero(contact->endpoint->subscription.mwi.mailboxes)) {
+ if (!endpoint || ast_strlen_zero(endpoint->subscription.mwi.mailboxes)) {
+ ao2_cleanup(endpoint);
return;
}
- ao2_lock(unsolicited_mwi);
+ /* Check if there is another contact */
+ found_contact = ast_sip_location_retrieve_contact_from_aor_list(endpoint->aors);
+ ao2_cleanup(endpoint);
+ if (found_contact) {
+ ao2_cleanup(found_contact);
+ return;
+ }
- mwi_subs = ao2_find(unsolicited_mwi, endpoint_id,
+ ao2_lock(unsolicited_mwi);
+ mwi_subs = ao2_find(unsolicited_mwi, contact->endpoint_name,
OBJ_SEARCH_KEY | OBJ_MULTIPLE | OBJ_NOLOCK | OBJ_UNLINK);
if (mwi_subs) {
for (; (mwi_sub = ao2_iterator_next(mwi_subs)); ao2_cleanup(mwi_sub)) {
@@ -1221,18 +1278,14 @@ static void mwi_contact_added(const void *object)
}
ao2_iterator_destroy(mwi_subs);
}
-
- create_mwi_subscriptions_for_endpoint(contact->endpoint, NULL, 0);
-
ao2_unlock(unsolicited_mwi);
-
- mwi_contact_updated(object);
}
/*! \brief Observer for contacts so unsolicited MWI is sent when a contact changes */
static const struct ast_sorcery_observer mwi_contact_observer = {
.created = mwi_contact_added,
.updated = mwi_contact_updated,
+ .deleted = mwi_contact_deleted,
};
/*! \brief Task invoked to send initial MWI NOTIFY for unsolicited */
diff --git a/res/res_pjsip_pidf_body_generator.c b/res/res_pjsip_pidf_body_generator.c
index 25f063915..cc10082ad 100644
--- a/res/res_pjsip_pidf_body_generator.c
+++ b/res/res_pjsip_pidf_body_generator.c
@@ -58,7 +58,7 @@ static int pidf_generate_body_content(void *body, void *data)
struct ast_sip_exten_state_data *state_data = data;
ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
- &pidfstate, &pidfnote, &local_state);
+ &pidfstate, &pidfnote, &local_state, 0);
if (!pjpidf_pres_add_note(state_data->pool, pres, pj_cstr(&note, pidfnote))) {
ast_log(LOG_WARNING, "Unable to add note to PIDF presence\n");
diff --git a/res/res_pjsip_pidf_eyebeam_body_supplement.c b/res/res_pjsip_pidf_eyebeam_body_supplement.c
index cc6dfd125..a0f50fdde 100644
--- a/res/res_pjsip_pidf_eyebeam_body_supplement.c
+++ b/res/res_pjsip_pidf_eyebeam_body_supplement.c
@@ -80,7 +80,7 @@ static int pidf_supplement_body(void *body, void *data)
enum ast_sip_pidf_state local_state;
ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
- &pidfstate, &pidfnote, &local_state);
+ &pidfstate, &pidfnote, &local_state, 0);
add_eyebeam(state_data->pool, pres, pidfstate);
return 0;
diff --git a/res/res_pjsip_sdp_rtp.c b/res/res_pjsip_sdp_rtp.c
index c5a673aa4..a2e7f8f92 100644
--- a/res/res_pjsip_sdp_rtp.c
+++ b/res/res_pjsip_sdp_rtp.c
@@ -51,6 +51,8 @@
#include "asterisk/sdp_srtp.h"
#include "asterisk/dsp.h"
#include "asterisk/linkedlists.h" /* for AST_LIST_NEXT */
+#include "asterisk/stream.h"
+#include "asterisk/format_cache.h"
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
@@ -62,48 +64,7 @@ static struct ast_sched_context *sched;
static struct ast_sockaddr address_rtp;
static const char STR_AUDIO[] = "audio";
-static const int FD_AUDIO = 0;
-
static const char STR_VIDEO[] = "video";
-static const int FD_VIDEO = 2;
-
-/*! \brief Retrieves an ast_format_type based on the given stream_type */
-static enum ast_media_type stream_to_media_type(const char *stream_type)
-{
- if (!strcasecmp(stream_type, STR_AUDIO)) {
- return AST_MEDIA_TYPE_AUDIO;
- } else if (!strcasecmp(stream_type, STR_VIDEO)) {
- return AST_MEDIA_TYPE_VIDEO;
- }
-
- return 0;
-}
-
-/*! \brief Get the starting descriptor for a media type */
-static int media_type_to_fdno(enum ast_media_type media_type)
-{
- switch (media_type) {
- case AST_MEDIA_TYPE_AUDIO: return FD_AUDIO;
- case AST_MEDIA_TYPE_VIDEO: return FD_VIDEO;
- case AST_MEDIA_TYPE_TEXT:
- case AST_MEDIA_TYPE_UNKNOWN:
- case AST_MEDIA_TYPE_IMAGE:
- case AST_MEDIA_TYPE_END: break;
- }
- return -1;
-}
-
-/*! \brief Remove all other cap types but the one given */
-static void format_cap_only_type(struct ast_format_cap *caps, enum ast_media_type media_type)
-{
- int i = 0;
- while (i <= AST_MEDIA_TYPE_TEXT) {
- if (i != media_type && i != AST_MEDIA_TYPE_UNKNOWN) {
- ast_format_cap_remove_by_type(caps, i);
- }
- i += 1;
- }
-}
static int send_keepalive(const void *data)
{
@@ -246,18 +207,18 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me
ice->stop(session_media->rtp);
}
- if (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733 || session->endpoint->dtmf == AST_SIP_DTMF_AUTO) {
+ if (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733 || session->endpoint->dtmf == AST_SIP_DTMF_AUTO || session->endpoint->dtmf == AST_SIP_DTMF_AUTO_INFO) {
ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_RFC2833);
ast_rtp_instance_set_prop(session_media->rtp, AST_RTP_PROPERTY_DTMF, 1);
} else if (session->endpoint->dtmf == AST_SIP_DTMF_INBAND) {
ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND);
}
- if (!strcmp(session_media->stream_type, STR_AUDIO) &&
+ if (session_media->type == AST_MEDIA_TYPE_AUDIO &&
(session->endpoint->media.tos_audio || session->endpoint->media.cos_audio)) {
ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_audio,
session->endpoint->media.cos_audio, "SIP RTP Audio");
- } else if (!strcmp(session_media->stream_type, STR_VIDEO) &&
+ } else if (session_media->type == AST_MEDIA_TYPE_VIDEO &&
(session->endpoint->media.tos_video || session->endpoint->media.cos_video)) {
ast_rtp_instance_set_qos(session_media->rtp, session->endpoint->media.tos_video,
session->endpoint->media.cos_video, "SIP RTP Video");
@@ -269,7 +230,7 @@ static int create_rtp(struct ast_sip_session *session, struct ast_sip_session_me
}
static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp_media *stream, struct ast_rtp_codecs *codecs,
- struct ast_sip_session_media *session_media)
+ struct ast_sip_session_media *session_media)
{
pjmedia_sdp_attr *attr;
pjmedia_sdp_rtpmap *rtpmap;
@@ -335,6 +296,16 @@ static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp
if (!tel_event && (session->endpoint->dtmf == AST_SIP_DTMF_AUTO)) {
ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_INBAND);
}
+
+ if (session->endpoint->dtmf == AST_SIP_DTMF_AUTO_INFO) {
+ if (tel_event) {
+ ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_RFC2833);
+ } else {
+ ast_rtp_instance_dtmf_mode_set(session_media->rtp, AST_RTP_DTMF_MODE_NONE);
+ }
+ }
+
+
/* Get the packetization, if it exists */
if ((attr = pjmedia_sdp_media_find_attr2(stream, "ptime", NULL))) {
unsigned long framing = pj_strtoul(pj_strltrim(&attr->value));
@@ -346,13 +317,15 @@ static void get_codecs(struct ast_sip_session *session, const struct pjmedia_sdp
static int set_caps(struct ast_sip_session *session,
struct ast_sip_session_media *session_media,
+ struct ast_sip_session_media *session_media_transport,
const struct pjmedia_sdp_media *stream,
- int is_offer)
+ int is_offer, struct ast_stream *asterisk_stream)
{
RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
RAII_VAR(struct ast_format_cap *, peer, NULL, ao2_cleanup);
RAII_VAR(struct ast_format_cap *, joint, NULL, ao2_cleanup);
- enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
+ RAII_VAR(struct ast_format_cap *, endpoint_caps, NULL, ao2_cleanup);
+ enum ast_media_type media_type = session_media->type;
struct ast_rtp_codecs codecs = AST_RTP_CODECS_NULL_INIT;
int fmts = 0;
int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
@@ -362,14 +335,14 @@ static int set_caps(struct ast_sip_session *session,
if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||
!(peer = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT)) ||
!(joint = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
- ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type);
+ ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",
+ ast_codec_media_type2str(session_media->type));
return -1;
}
/* get the endpoint capabilities */
if (direct_media_enabled) {
ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps);
- format_cap_only_type(caps, media_type);
} else {
ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, media_type);
}
@@ -386,7 +359,7 @@ static int set_caps(struct ast_sip_session *session,
ast_rtp_codecs_payloads_destroy(&codecs);
ast_log(LOG_NOTICE, "No joint capabilities for '%s' media stream between our configuration(%s) and incoming SDP(%s)\n",
- session_media->stream_type,
+ ast_codec_media_type2str(session_media->type),
ast_format_cap_get_names(caps, &usbuf),
ast_format_cap_get_names(peer, &thembuf));
return -1;
@@ -402,9 +375,27 @@ static int set_caps(struct ast_sip_session *session,
ast_rtp_codecs_payloads_copy(&codecs, ast_rtp_instance_get_codecs(session_media->rtp),
session_media->rtp);
- ast_format_cap_append_from_cap(session->req_caps, joint, AST_MEDIA_TYPE_UNKNOWN);
+ ast_stream_set_formats(asterisk_stream, joint);
+
+ /* If this is a bundled stream then apply the payloads to RTP instance acting as transport to prevent conflicts */
+ if (session_media_transport != session_media && session_media->bundled) {
+ int index;
+
+ for (index = 0; index < ast_format_cap_count(joint); ++index) {
+ struct ast_format *format = ast_format_cap_get_format(joint, index);
+ int rtp_code;
+
+ /* Ensure this payload is in the bundle group transport codecs, this purposely doesn't check the return value for
+ * things as the format is guaranteed to have a payload already.
+ */
+ rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, format, 0);
+ ast_rtp_codecs_payload_set_rx(ast_rtp_instance_get_codecs(session_media_transport->rtp), rtp_code, format);
+
+ ao2_ref(format, -1);
+ }
+ }
- if (session->channel) {
+ if (session->channel && ast_sip_session_is_pending_stream_default(session, asterisk_stream)) {
ast_channel_lock(session->channel);
ast_format_cap_remove_by_type(caps, AST_MEDIA_TYPE_UNKNOWN);
ast_format_cap_append_from_cap(caps, ast_channel_nativeformats(session->channel),
@@ -442,7 +433,8 @@ static int set_caps(struct ast_sip_session *session,
ast_set_read_format(session->channel, ast_channel_readformat(session->channel));
ast_set_write_format(session->channel, ast_channel_writeformat(session->channel));
}
- if ((session->endpoint->dtmf == AST_SIP_DTMF_AUTO)
+
+ if ( ((session->endpoint->dtmf == AST_SIP_DTMF_AUTO) || (session->endpoint->dtmf == AST_SIP_DTMF_AUTO_INFO) )
&& (ast_rtp_instance_dtmf_mode_get(session_media->rtp) == AST_RTP_DTMF_MODE_RFC2833)
&& (session->dsp)) {
dsp_features = ast_dsp_get_features(session->dsp);
@@ -523,7 +515,8 @@ static pjmedia_sdp_attr* generate_fmtp_attr(pj_pool_t *pool, struct ast_format *
}
/*! \brief Function which adds ICE attributes to a media stream */
-static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media)
+static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media, pj_pool_t *pool, pjmedia_sdp_media *media,
+ unsigned int include_candidates)
{
struct ast_rtp_engine_ice *ice;
struct ao2_container *candidates;
@@ -533,8 +526,7 @@ static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_se
struct ao2_iterator it_candidates;
struct ast_rtp_engine_ice_candidate *candidate;
- if (!session->endpoint->media.rtp.ice_support || !(ice = ast_rtp_instance_get_ice(session_media->rtp)) ||
- !(candidates = ice->get_local_candidates(session_media->rtp))) {
+ if (!session->endpoint->media.rtp.ice_support || !(ice = ast_rtp_instance_get_ice(session_media->rtp))) {
return;
}
@@ -548,6 +540,15 @@ static void add_ice_to_stream(struct ast_sip_session *session, struct ast_sip_se
media->attr[media->attr_count++] = attr;
}
+ if (!include_candidates) {
+ return;
+ }
+
+ candidates = ice->get_local_candidates(session_media->rtp);
+ if (!candidates) {
+ return;
+ }
+
it_candidates = ao2_iterator_init(candidates, 0);
for (; (candidate = ao2_iterator_next(&it_candidates)); ao2_ref(candidate, -1)) {
struct ast_str *attr_candidate = ast_str_create(128);
@@ -967,25 +968,139 @@ static void set_ice_components(struct ast_sip_session *session, struct ast_sip_s
}
}
+/*! \brief Function which adds ssrc attributes to a media stream */
+static void add_ssrc_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;
+ char tmp[128];
+
+ if (!session->endpoint->media.bundle || session_media->bundle_group == -1) {
+ return;
+ }
+
+ snprintf(tmp, sizeof(tmp), "%u cname:%s", ast_rtp_instance_get_ssrc(session_media->rtp), ast_rtp_instance_get_cname(session_media->rtp));
+ attr = pjmedia_sdp_attr_create(pool, "ssrc", pj_cstr(&stmp, tmp));
+ media->attr[media->attr_count++] = attr;
+}
+
+/*! \brief Function which processes ssrc attributes in a stream */
+static void process_ssrc_attributes(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ const struct pjmedia_sdp_media *remote_stream)
+{
+ int index;
+
+ if (!session->endpoint->media.bundle) {
+ return;
+ }
+
+ for (index = 0; index < remote_stream->attr_count; ++index) {
+ pjmedia_sdp_attr *attr = remote_stream->attr[index];
+ char attr_value[pj_strlen(&attr->value) + 1];
+ char *ssrc_attribute_name, *ssrc_attribute_value = NULL;
+ unsigned int ssrc;
+
+ /* We only care about ssrc attributes */
+ if (pj_strcmp2(&attr->name, "ssrc")) {
+ continue;
+ }
+
+ ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value));
+
+ if ((ssrc_attribute_name = strchr(attr_value, ' '))) {
+ /* This has an actual attribute */
+ *ssrc_attribute_name++ = '\0';
+ ssrc_attribute_value = strchr(ssrc_attribute_name, ':');
+ if (ssrc_attribute_value) {
+ /* Values are actually optional according to the spec */
+ *ssrc_attribute_value++ = '\0';
+ }
+ }
+
+ if (sscanf(attr_value, "%30u", &ssrc) < 1) {
+ continue;
+ }
+
+ ast_rtp_instance_set_remote_ssrc(session_media->rtp, ssrc);
+ }
+}
+
+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 struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_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,
+ int index, struct ast_stream *asterisk_stream)
{
char host[NI_MAXHOST];
RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
- enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
+ pjmedia_sdp_media *stream = sdp->media[index];
+ struct ast_sip_session_media *session_media_transport;
+ enum ast_media_type media_type = session_media->type;
enum ast_sip_session_media_encryption encryption = AST_SIP_MEDIA_ENCRYPT_NONE;
int res;
- /* If port is 0, ignore this media stream */
- if (!stream->desc.port) {
- ast_debug(3, "Media stream '%s' is already declined\n", session_media->stream_type);
- return 0;
- }
-
/* If no type formats have been configured reject this stream */
if (!ast_format_cap_has_type(session->endpoint->media.codecs, media_type)) {
- ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n", session_media->stream_type);
+ ast_debug(3, "Endpoint has no codecs for media type '%s', declining stream\n",
+ ast_codec_media_type2str(session_media->type));
return 0;
}
@@ -1011,38 +1126,51 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct
return -1;
}
- session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(stream, "rtcp-mux", NULL) != NULL);
- set_ice_components(session, session_media);
+ 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);
- enable_rtcp(session, session_media, stream);
+ if (session_media_transport == session_media || !session_media->bundled) {
+ /* If this media session is carrying actual traffic then set up those aspects */
+ session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(stream, "rtcp-mux", NULL) != NULL);
+ set_ice_components(session, session_media);
- res = setup_media_encryption(session, session_media, sdp, stream);
- if (res) {
- if (!session->endpoint->media.rtp.encryption_optimistic ||
- !pj_strncmp2(&stream->desc.transport, "RTP/SAVP", 8)) {
- /* If optimistic encryption is disabled and crypto should have been enabled
- * but was not this session must fail. This must also fail if crypto was
- * required in the offer but could not be set up.
- */
- return -1;
+ enable_rtcp(session, session_media, stream);
+
+ res = setup_media_encryption(session, session_media, sdp, stream);
+ if (res) {
+ if (!session->endpoint->media.rtp.encryption_optimistic ||
+ !pj_strncmp2(&stream->desc.transport, "RTP/SAVP", 8)) {
+ /* If optimistic encryption is disabled and crypto should have been enabled
+ * but was not this session must fail. This must also fail if crypto was
+ * required in the offer but could not be set up.
+ */
+ return -1;
+ }
+ /* There is no encryption, sad. */
+ session_media->encryption = AST_SIP_MEDIA_ENCRYPT_NONE;
}
- /* There is no encryption, sad. */
- session_media->encryption = AST_SIP_MEDIA_ENCRYPT_NONE;
- }
- /* If we've been explicitly configured to use the received transport OR if
- * encryption is on and crypto is present use the received transport.
- * This is done in case of optimistic because it may come in as RTP/AVP or RTP/SAVP depending
- * on the configuration of the remote endpoint (optimistic themselves or mandatory).
- */
- if ((session->endpoint->media.rtp.use_received_transport) ||
- ((encryption == AST_SIP_MEDIA_ENCRYPT_SDES) && !res)) {
- pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport);
- }
+ /* If we've been explicitly configured to use the received transport OR if
+ * encryption is on and crypto is present use the received transport.
+ * This is done in case of optimistic because it may come in as RTP/AVP or RTP/SAVP depending
+ * on the configuration of the remote endpoint (optimistic themselves or mandatory).
+ */
+ if ((session->endpoint->media.rtp.use_received_transport) ||
+ ((encryption == AST_SIP_MEDIA_ENCRYPT_SDES) && !res)) {
+ pj_strdup(session->inv_session->pool, &session_media->transport, &stream->desc.transport);
+ }
+ } else {
+ /* This is bundled with another session, so mark it as such */
+ ast_rtp_instance_bundle(session_media->rtp, session_media_transport->rtp);
+
+ enable_rtcp(session, session_media, stream);
+ }
- if (set_caps(session, session_media, stream, 1)) {
+ if (set_caps(session, session_media, session_media_transport, stream, 1, asterisk_stream)) {
return 0;
}
+
return 1;
}
@@ -1062,6 +1190,7 @@ static int add_crypto_to_stream(struct ast_sip_session *session,
static const pj_str_t STR_PASSIVE = { "passive", 7 };
static const pj_str_t STR_ACTPASS = { "actpass", 7 };
static const pj_str_t STR_HOLDCONN = { "holdconn", 8 };
+ enum ast_rtp_dtls_setup setup;
switch (session_media->encryption) {
case AST_SIP_MEDIA_ENCRYPT_NONE:
@@ -1115,7 +1244,16 @@ static int add_crypto_to_stream(struct ast_sip_session *session,
break;
}
- switch (dtls->get_setup(session_media->rtp)) {
+ /* If this is an answer we need to use our current state, if it's an offer we need to use
+ * the configured value.
+ */
+ if (pjmedia_sdp_neg_get_state(session->inv_session->neg) != PJMEDIA_SDP_NEG_STATE_DONE) {
+ setup = dtls->get_setup(session_media->rtp);
+ } else {
+ setup = session->endpoint->media.rtp.dtls_cfg.default_setup;
+ }
+
+ switch (setup) {
case AST_RTP_DTLS_SETUP_ACTIVE:
attr = pjmedia_sdp_attr_create(pool, "setup", &STR_ACTIVE);
media->attr[media->attr_count++] = attr;
@@ -1130,7 +1268,6 @@ static int add_crypto_to_stream(struct ast_sip_session *session,
break;
case AST_RTP_DTLS_SETUP_HOLDCONN:
attr = pjmedia_sdp_attr_create(pool, "setup", &STR_HOLDCONN);
- media->attr[media->attr_count++] = attr;
break;
default:
break;
@@ -1161,9 +1298,10 @@ static int add_crypto_to_stream(struct ast_sip_session *session,
/*! \brief Function which creates an outgoing stream */
static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
- struct pjmedia_sdp_session *sdp)
+ struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_session *remote, struct ast_stream *stream)
{
pj_pool_t *pool = session->inv_session->pool_prov;
+ static const pj_str_t STR_RTP_AVP = { "RTP/AVP", 7 };
static const pj_str_t STR_IN = { "IN", 2 };
static const pj_str_t STR_IP4 = { "IP4", 3};
static const pj_str_t STR_IP6 = { "IP6", 3};
@@ -1176,96 +1314,166 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
pj_str_t stmp;
pjmedia_sdp_attr *attr;
int index = 0;
- int noncodec = (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733 || session->endpoint->dtmf == AST_SIP_DTMF_AUTO) ? AST_RTP_DTMF : 0;
+ int noncodec = (session->endpoint->dtmf == AST_SIP_DTMF_RFC_4733 || session->endpoint->dtmf == AST_SIP_DTMF_AUTO || session->endpoint->dtmf == AST_SIP_DTMF_AUTO_INFO) ? AST_RTP_DTMF : 0;
int min_packet_size = 0, max_packet_size = 0;
int rtp_code;
RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
- enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
- int use_override_prefs = ast_format_cap_count(session->req_caps);
+ enum ast_media_type media_type = session_media->type;
+ struct ast_sip_session_media *session_media_transport;
int direct_media_enabled = !ast_sockaddr_isnull(&session_media->direct_media_addr) &&
ast_format_cap_count(session->direct_media_cap);
- if ((use_override_prefs && !ast_format_cap_has_type(session->req_caps, media_type)) ||
- (!use_override_prefs && !ast_format_cap_has_type(session->endpoint->media.codecs, media_type))) {
- /* If no type formats are configured don't add a stream */
- return 0;
- } else if (!session_media->rtp && create_rtp(session, session_media)) {
+ media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media));
+ if (!media) {
return -1;
}
+ pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type));
- set_ice_components(session, session_media);
- enable_rtcp(session, session_media, NULL);
+ /* If this is a removed (or declined) stream OR if no formats exist then construct a minimal stream in SDP */
+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED || !ast_stream_get_formats(stream) ||
+ !ast_format_cap_count(ast_stream_get_formats(stream))) {
+ media->desc.port = 0;
+ media->desc.port_count = 1;
- if (!(media = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_media))) ||
- !(media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn)))) {
- return -1;
- }
+ if (remote) {
+ pjmedia_sdp_media *remote_media = remote->media[ast_stream_get_position(stream)];
+ int index;
- if (add_crypto_to_stream(session, session_media, pool, media)) {
- return -1;
- }
+ media->desc.transport = remote_media->desc.transport;
- media->desc.media = pj_str(session_media->stream_type);
- if (pj_strlen(&session_media->transport)) {
- /* If a transport has already been specified use it */
- media->desc.transport = session_media->transport;
- } else {
- media->desc.transport = pj_str(ast_sdp_get_rtp_profile(
- /* Optimistic encryption places crypto in the normal RTP/AVP profile */
- !session->endpoint->media.rtp.encryption_optimistic &&
- (session_media->encryption == AST_SIP_MEDIA_ENCRYPT_SDES),
- session_media->rtp, session->endpoint->media.rtp.use_avpf,
- session->endpoint->media.rtp.force_avp));
- }
+ /* Preserve existing behavior by copying the formats provided from the offer */
+ for (index = 0; index < remote_media->desc.fmt_count; ++index) {
+ media->desc.fmt[index] = remote_media->desc.fmt[index];
+ }
+ media->desc.fmt_count = remote_media->desc.fmt_count;
+ } else {
+ /* This is actually an offer so put a dummy payload in that is ignored and sane transport */
+ media->desc.transport = STR_RTP_AVP;
+ pj_strdup2(pool, &media->desc.fmt[media->desc.fmt_count++], "32");
+ }
- /* Add connection level details */
- if (direct_media_enabled) {
- hostip = ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR);
- } else if (ast_strlen_zero(session->endpoint->media.address)) {
- hostip = ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET());
- } else {
- hostip = session->endpoint->media.address;
+ sdp->media[sdp->media_count++] = media;
+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+
+ return 1;
}
- if (ast_strlen_zero(hostip)) {
- ast_log(LOG_ERROR, "No local host IP available for stream %s\n", session_media->stream_type);
+ if (!session_media->rtp && create_rtp(session, session_media)) {
return -1;
}
- media->conn->net_type = STR_IN;
- /* Assume that the connection will use IPv4 until proven otherwise */
- media->conn->addr_type = STR_IP4;
- pj_strdup2(pool, &media->conn->addr, hostip);
+ /* If this stream has not been bundled already it is new and we need to ensure there is no SSRC conflict */
+ if (session_media->bundle_group != -1 && !session_media->bundled) {
+ for (index = 0; index < sdp->media_count; ++index) {
+ struct ast_sip_session_media *other_session_media;
- if (!ast_strlen_zero(session->endpoint->media.address)) {
- pj_sockaddr ip;
+ other_session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index);
+ if (!other_session_media->rtp || other_session_media->bundle_group != session_media->bundle_group) {
+ continue;
+ }
- if ((pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &media->conn->addr, &ip) == PJ_SUCCESS) &&
- (ip.addr.sa_family == pj_AF_INET6())) {
- media->conn->addr_type = STR_IP6;
+ if (ast_rtp_instance_get_ssrc(session_media->rtp) == ast_rtp_instance_get_ssrc(other_session_media->rtp)) {
+ ast_rtp_instance_change_source(session_media->rtp);
+ /* Start the conflict check over again */
+ index = -1;
+ continue;
+ }
}
}
- ast_rtp_instance_get_local_address(session_media->rtp, &addr);
- media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr);
- media->desc.port_count = 1;
+ session_media_transport = ast_sip_session_media_get_transport(session, session_media);
+
+ if (session_media_transport == session_media || !session_media->bundled) {
+ set_ice_components(session, session_media);
+ enable_rtcp(session, session_media, NULL);
+
+ /* Crypto has to be added before setting the media transport so that SRTP is properly
+ * set up according to the configuration. This ends up changing the media transport.
+ */
+ if (add_crypto_to_stream(session, session_media, pool, media)) {
+ return -1;
+ }
+
+ if (pj_strlen(&session_media->transport)) {
+ /* If a transport has already been specified use it */
+ media->desc.transport = session_media->transport;
+ } else {
+ media->desc.transport = pj_str(ast_sdp_get_rtp_profile(
+ /* Optimistic encryption places crypto in the normal RTP/AVP profile */
+ !session->endpoint->media.rtp.encryption_optimistic &&
+ (session_media->encryption == AST_SIP_MEDIA_ENCRYPT_SDES),
+ session_media->rtp, session->endpoint->media.rtp.use_avpf,
+ session->endpoint->media.rtp.force_avp));
+ }
+
+ media->conn = pj_pool_zalloc(pool, sizeof(struct pjmedia_sdp_conn));
+ if (!media->conn) {
+ return -1;
+ }
+
+ /* Add connection level details */
+ if (direct_media_enabled) {
+ hostip = ast_sockaddr_stringify_fmt(&session_media->direct_media_addr, AST_SOCKADDR_STR_ADDR);
+ } else if (ast_strlen_zero(session->endpoint->media.address)) {
+ hostip = ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET());
+ } else {
+ hostip = session->endpoint->media.address;
+ }
+
+ if (ast_strlen_zero(hostip)) {
+ ast_log(LOG_ERROR, "No local host IP available for stream %s\n",
+ ast_codec_media_type2str(session_media->type));
+ return -1;
+ }
+
+ media->conn->net_type = STR_IN;
+ /* Assume that the connection will use IPv4 until proven otherwise */
+ media->conn->addr_type = STR_IP4;
+ pj_strdup2(pool, &media->conn->addr, hostip);
+
+ if (!ast_strlen_zero(session->endpoint->media.address)) {
+ pj_sockaddr ip;
+
+ if ((pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &media->conn->addr, &ip) == PJ_SUCCESS) &&
+ (ip.addr.sa_family == pj_AF_INET6())) {
+ media->conn->addr_type = STR_IP6;
+ }
+ }
+
+ /* Add ICE attributes and candidates */
+ add_ice_to_stream(session, session_media, pool, media, 1);
+
+ ast_rtp_instance_get_local_address(session_media->rtp, &addr);
+ media->desc.port = direct_media_enabled ? ast_sockaddr_port(&session_media->direct_media_addr) : (pj_uint16_t) ast_sockaddr_port(&addr);
+ media->desc.port_count = 1;
+ } else {
+ pjmedia_sdp_media *bundle_group_stream = sdp->media[session_media_transport->stream_num];
+
+ /* As this is in a bundle group it shares the same details as the group instance */
+ media->desc.transport = bundle_group_stream->desc.transport;
+ media->conn = bundle_group_stream->conn;
+ media->desc.port = bundle_group_stream->desc.port;
+
+ if (add_crypto_to_stream(session, session_media_transport, pool, media)) {
+ return -1;
+ }
- /* Add ICE attributes and candidates */
- add_ice_to_stream(session, session_media, pool, media);
+ add_ice_to_stream(session, session_media_transport, pool, media, 0);
+
+ enable_rtcp(session, session_media, NULL);
+ }
if (!(caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT))) {
- ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n", session_media->stream_type);
+ ast_log(LOG_ERROR, "Failed to allocate %s capabilities\n",
+ ast_codec_media_type2str(session_media->type));
return -1;
}
if (direct_media_enabled) {
ast_format_cap_get_compatible(session->endpoint->media.codecs, session->direct_media_cap, caps);
- } else if (!ast_format_cap_count(session->req_caps) ||
- !ast_format_cap_iscompatible(session->req_caps, session->endpoint->media.codecs)) {
- ast_format_cap_append_from_cap(caps, session->endpoint->media.codecs, media_type);
} else {
- ast_format_cap_append_from_cap(caps, session->req_caps, media_type);
+ ast_format_cap_append_from_cap(caps, ast_stream_get_formats(stream), media_type);
}
for (index = 0; index < ast_format_cap_count(caps); ++index) {
@@ -1276,10 +1484,23 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
continue;
}
- if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, format, 0)) == -1) {
- ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", ast_format_get_name(format));
- ao2_ref(format, -1);
- continue;
+ /* If this stream is not a transport we need to use the transport codecs structure for payload management to prevent
+ * conflicts.
+ */
+ if (session_media_transport != session_media) {
+ if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media_transport->rtp), 1, format, 0)) == -1) {
+ ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", ast_format_get_name(format));
+ ao2_ref(format, -1);
+ continue;
+ }
+ /* Our instance has to match the payload number though */
+ ast_rtp_codecs_payload_set_rx(ast_rtp_instance_get_codecs(session_media->rtp), rtp_code, format);
+ } else {
+ if ((rtp_code = ast_rtp_codecs_payload_code(ast_rtp_instance_get_codecs(session_media->rtp), 1, format, 0)) == -1) {
+ ast_log(LOG_WARNING,"Unable to get rtp codec payload code for %s\n", ast_format_get_name(format));
+ ao2_ref(format, -1);
+ continue;
+ }
}
if ((attr = generate_rtpmap_attr(session, media, pool, rtp_code, 1, format, 0))) {
@@ -1302,7 +1523,8 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
}
/* Add non-codec formats */
- if (media_type != AST_MEDIA_TYPE_VIDEO && media->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) {
+ if (ast_sip_session_is_pending_stream_default(session, stream) && media_type != AST_MEDIA_TYPE_VIDEO
+ && media->desc.fmt_count < PJMEDIA_MAX_SDP_FMT) {
for (index = 1LL; index <= AST_RTP_MAX; index <<= 1) {
if (!(noncodec & index)) {
continue;
@@ -1329,6 +1551,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
}
}
+
/* If no formats were actually added to the media stream don't add it to the SDP */
if (!media->desc.fmt_count) {
return 1;
@@ -1362,29 +1585,76 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
}
+ 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;
return 1;
}
-static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
- const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
- const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream)
+static struct ast_frame *media_session_rtp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+ struct ast_frame *f;
+
+ if (!session_media->rtp) {
+ return &ast_null_frame;
+ }
+
+ f = ast_rtp_instance_read(session_media->rtp, 0);
+ if (!f) {
+ return NULL;
+ }
+
+ ast_rtp_instance_set_last_rx(session_media->rtp, time(NULL));
+
+ return f;
+}
+
+static struct ast_frame *media_session_rtcp_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+ struct ast_frame *f;
+
+ if (!session_media->rtp) {
+ return &ast_null_frame;
+ }
+
+ f = ast_rtp_instance_read(session_media->rtp, 1);
+ if (!f) {
+ return NULL;
+ }
+
+ ast_rtp_instance_set_last_rx(session_media->rtp, time(NULL));
+
+ return f;
+}
+
+static int media_session_rtp_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame)
+{
+ if (!session_media->rtp) {
+ return 0;
+ }
+
+ return ast_rtp_instance_write(session_media->rtp, frame);
+}
+
+static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
+ struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local,
+ const struct pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)
{
RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
- enum ast_media_type media_type = stream_to_media_type(session_media->stream_type);
+ struct pjmedia_sdp_media *remote_stream = remote->media[index];
+ enum ast_media_type media_type = session_media->type;
char host[NI_MAXHOST];
- int fdno, res;
+ int res;
+ struct ast_sip_session_media *session_media_transport;
if (!session->channel) {
return 1;
}
- if (!local_stream->desc.port || !remote_stream->desc.port) {
- return 1;
- }
-
/* Ensure incoming transport is compatible with the endpoint's configuration */
if (!session->endpoint->media.rtp.use_received_transport &&
check_endpoint_media_transport(session->endpoint, remote_stream) == AST_SIP_MEDIA_TRANSPORT_INVALID) {
@@ -1396,50 +1666,68 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a
return -1;
}
- session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(remote_stream, "rtcp-mux", NULL) != NULL);
- set_ice_components(session, session_media);
+ process_ssrc_attributes(session, session_media, remote_stream);
- enable_rtcp(session, session_media, remote_stream);
+ session_media_transport = ast_sip_session_media_get_transport(session, session_media);
- res = setup_media_encryption(session, session_media, remote, remote_stream);
- if (!session->endpoint->media.rtp.encryption_optimistic && res) {
- /* If optimistic encryption is disabled and crypto should have been enabled but was not
- * this session must fail.
- */
- return -1;
- }
+ if (session_media_transport == session_media || !session_media->bundled) {
+ session_media->remote_rtcp_mux = (pjmedia_sdp_media_find_attr2(remote_stream, "rtcp-mux", NULL) != NULL);
+ set_ice_components(session, session_media);
- if (!remote_stream->conn && !remote->conn) {
- return 1;
- }
+ enable_rtcp(session, session_media, remote_stream);
- ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host));
+ res = setup_media_encryption(session, session_media, remote, remote_stream);
+ if (!session->endpoint->media.rtp.encryption_optimistic && res) {
+ /* If optimistic encryption is disabled and crypto should have been enabled but was not
+ * this session must fail.
+ */
+ return -1;
+ }
- /* Ensure that the address provided is valid */
- if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC) <= 0) {
- /* The provided host was actually invalid so we error out this negotiation */
- return -1;
- }
+ if (!remote_stream->conn && !remote->conn) {
+ return 1;
+ }
- /* Apply connection information to the RTP instance */
- ast_sockaddr_set_port(addrs, remote_stream->desc.port);
- ast_rtp_instance_set_remote_address(session_media->rtp, addrs);
- if (set_caps(session, session_media, remote_stream, 0)) {
- return 1;
- }
+ ast_copy_pj_str(host, remote_stream->conn ? &remote_stream->conn->addr : &remote->conn->addr, sizeof(host));
- if ((fdno = media_type_to_fdno(media_type)) < 0) {
- return -1;
+ /* Ensure that the address provided is valid */
+ if (ast_sockaddr_resolve(&addrs, host, PARSE_PORT_FORBID, AST_AF_UNSPEC) <= 0) {
+ /* The provided host was actually invalid so we error out this negotiation */
+ return -1;
+ }
+
+ /* Apply connection information to the RTP instance */
+ ast_sockaddr_set_port(addrs, remote_stream->desc.port);
+ ast_rtp_instance_set_remote_address(session_media->rtp, addrs);
+
+ ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback);
+ ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 0),
+ media_session_rtp_read_callback);
+ if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) {
+ ast_sip_session_media_add_read_callback(session, session_media, ast_rtp_instance_fd(session_media->rtp, 1),
+ media_session_rtcp_read_callback);
+ }
+
+ /* If ICE support is enabled find all the needed attributes */
+ process_ice_attributes(session, session_media, remote, remote_stream);
+ } else {
+ /* This is bundled with another session, so mark it as such */
+ ast_rtp_instance_bundle(session_media->rtp, session_media_transport->rtp);
+ ast_sip_session_media_set_write_callback(session, session_media, media_session_rtp_write_callback);
+ enable_rtcp(session, session_media, remote_stream);
}
- ast_channel_set_fd(session->channel, fdno, ast_rtp_instance_fd(session_media->rtp, 0));
- if (!session->endpoint->media.rtcp_mux || !session_media->remote_rtcp_mux) {
- ast_channel_set_fd(session->channel, fdno + 1, ast_rtp_instance_fd(session_media->rtp, 1));
+
+ if (set_caps(session, session_media, session_media_transport, remote_stream, 0, asterisk_stream)) {
+ return 1;
}
- /* If ICE support is enabled find all the needed attributes */
- process_ice_attributes(session, session_media, remote, remote_stream);
+ /* Set the channel uniqueid on the RTP instance now that it is becoming active */
+ ast_channel_lock(session->channel);
+ ast_rtp_instance_set_channel_id(session_media->rtp, ast_channel_uniqueid(session->channel));
+ ast_channel_unlock(session->channel);
/* Ensure the RTP instance is active */
+ ast_rtp_instance_set_stream_num(session_media->rtp, ast_stream_get_position(asterisk_stream));
ast_rtp_instance_activate(session_media->rtp);
/* audio stream handles music on hold */
@@ -1476,7 +1764,7 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a
session_media->encryption = session->endpoint->media.rtp.encryption;
if (session->endpoint->media.rtp.keepalive > 0 &&
- stream_to_media_type(session_media->stream_type) == AST_MEDIA_TYPE_AUDIO) {
+ session_media->type == AST_MEDIA_TYPE_AUDIO) {
ast_rtp_instance_set_keepalive(session_media->rtp, session->endpoint->media.rtp.keepalive);
/* Schedule the initial keepalive early in case this is being used to punch holes through
* a NAT. This way there won't be an awkward delay before media starts flowing in some
diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c
index ffd01cadf..0ad2c8f30 100644
--- a/res/res_pjsip_session.c
+++ b/res/res_pjsip_session.c
@@ -47,12 +47,16 @@
#include "asterisk/features_config.h"
#include "asterisk/pickup.h"
#include "asterisk/test.h"
+#include "asterisk/stream.h"
#define SDP_HANDLER_BUCKETS 11
#define MOD_DATA_ON_RESPONSE "on_response"
#define MOD_DATA_NAT_HOOK "nat_hook"
+/* Most common case is one audio and one video stream */
+#define DEFAULT_NUM_SESSION_MEDIA 2
+
/* Some forward declarations */
static void handle_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata);
static void handle_incoming_response(struct ast_sip_session *session, pjsip_rx_data *rdata,
@@ -103,23 +107,6 @@ static int sdp_handler_list_cmp(void *obj, void *arg, int flags)
return strcmp(handler_list1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP;
}
-static int session_media_hash(const void *obj, int flags)
-{
- const struct ast_sip_session_media *session_media = obj;
- const char *stream_type = flags & OBJ_KEY ? obj : session_media->stream_type;
-
- return ast_str_hash(stream_type);
-}
-
-static int session_media_cmp(void *obj, void *arg, int flags)
-{
- struct ast_sip_session_media *session_media1 = obj;
- struct ast_sip_session_media *session_media2 = arg;
- const char *stream_type2 = flags & OBJ_KEY ? arg : session_media2->stream_type;
-
- return strcmp(session_media1->stream_type, stream_type2) ? 0 : CMP_MATCH | CMP_STOP;
-}
-
int ast_sip_session_register_sdp_handler(struct ast_sip_session_sdp_handler *handler, const char *stream_type)
{
RAII_VAR(struct sdp_handler_list *, handler_list,
@@ -187,6 +174,178 @@ void ast_sip_session_unregister_sdp_handler(struct ast_sip_session_sdp_handler *
ao2_callback_data(sdp_handlers, OBJ_KEY | OBJ_UNLINK | OBJ_NODATA, remove_handler, (void *)stream_type, handler);
}
+struct ast_sip_session_media_state *ast_sip_session_media_state_alloc(void)
+{
+ struct ast_sip_session_media_state *media_state;
+
+ media_state = ast_calloc(1, sizeof(*media_state));
+ if (!media_state) {
+ return NULL;
+ }
+
+ if (AST_VECTOR_INIT(&media_state->sessions, DEFAULT_NUM_SESSION_MEDIA) < 0) {
+ ast_free(media_state);
+ return NULL;
+ }
+
+ if (AST_VECTOR_INIT(&media_state->read_callbacks, DEFAULT_NUM_SESSION_MEDIA) < 0) {
+ AST_VECTOR_FREE(&media_state->sessions);
+ ast_free(media_state);
+ return NULL;
+ }
+
+ return media_state;
+}
+
+void ast_sip_session_media_state_reset(struct ast_sip_session_media_state *media_state)
+{
+ int index;
+
+ if (!media_state) {
+ return;
+ }
+
+ AST_VECTOR_RESET(&media_state->sessions, ao2_cleanup);
+ AST_VECTOR_RESET(&media_state->read_callbacks, AST_VECTOR_ELEM_CLEANUP_NOOP);
+
+ for (index = 0; index < AST_MEDIA_TYPE_END; ++index) {
+ media_state->default_session[index] = NULL;
+ }
+
+ ast_stream_topology_free(media_state->topology);
+ media_state->topology = NULL;
+}
+
+struct ast_sip_session_media_state *ast_sip_session_media_state_clone(const struct ast_sip_session_media_state *media_state)
+{
+ struct ast_sip_session_media_state *cloned;
+ int index;
+
+ if (!media_state) {
+ return NULL;
+ }
+
+ cloned = ast_sip_session_media_state_alloc();
+ if (!cloned) {
+ return NULL;
+ }
+
+ if (media_state->topology) {
+ cloned->topology = ast_stream_topology_clone(media_state->topology);
+ if (!cloned->topology) {
+ ast_sip_session_media_state_free(cloned);
+ return NULL;
+ }
+ }
+
+ for (index = 0; index < AST_VECTOR_SIZE(&media_state->sessions); ++index) {
+ struct ast_sip_session_media *session_media = AST_VECTOR_GET(&media_state->sessions, index);
+ enum ast_media_type type = ast_stream_get_type(ast_stream_topology_get_stream(cloned->topology, index));
+
+ AST_VECTOR_REPLACE(&cloned->sessions, index, ao2_bump(session_media));
+ if (ast_stream_get_state(ast_stream_topology_get_stream(cloned->topology, index)) != AST_STREAM_STATE_REMOVED &&
+ !cloned->default_session[type]) {
+ cloned->default_session[type] = session_media;
+ }
+ }
+
+ for (index = 0; index < AST_VECTOR_SIZE(&media_state->read_callbacks); ++index) {
+ struct ast_sip_session_media_read_callback_state *read_callback = AST_VECTOR_GET_ADDR(&media_state->read_callbacks, index);
+
+ AST_VECTOR_REPLACE(&cloned->read_callbacks, index, *read_callback);
+ }
+
+ return cloned;
+}
+
+void ast_sip_session_media_state_free(struct ast_sip_session_media_state *media_state)
+{
+ if (!media_state) {
+ return;
+ }
+
+ /* This will reset the internal state so we only have to free persistent things */
+ ast_sip_session_media_state_reset(media_state);
+
+ AST_VECTOR_FREE(&media_state->sessions);
+ AST_VECTOR_FREE(&media_state->read_callbacks);
+
+ ast_free(media_state);
+}
+
+int ast_sip_session_is_pending_stream_default(const struct ast_sip_session *session, const struct ast_stream *stream)
+{
+ int index;
+
+ ast_assert(session->pending_media_state->topology != NULL);
+
+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
+ return 0;
+ }
+
+ for (index = 0; index < ast_stream_topology_get_count(session->pending_media_state->topology); ++index) {
+ if (ast_stream_get_type(ast_stream_topology_get_stream(session->pending_media_state->topology, index)) !=
+ ast_stream_get_type(stream)) {
+ continue;
+ }
+
+ return ast_stream_topology_get_stream(session->pending_media_state->topology, index) == stream ? 1 : 0;
+ }
+
+ return 0;
+}
+
+int ast_sip_session_media_add_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ int fd, ast_sip_session_media_read_cb callback)
+{
+ struct ast_sip_session_media_read_callback_state callback_state = {
+ .fd = fd,
+ .read_callback = callback,
+ .session = session_media,
+ };
+
+ /* The contents of the vector are whole structs and not pointers */
+ return AST_VECTOR_APPEND(&session->pending_media_state->read_callbacks, callback_state);
+}
+
+int ast_sip_session_media_set_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
+ ast_sip_session_media_write_cb callback)
+{
+ if (session_media->write_callback) {
+ if (session_media->write_callback == callback) {
+ return 0;
+ }
+
+ return -1;
+ }
+
+ session_media->write_callback = callback;
+
+ return 0;
+}
+
+struct ast_sip_session_media *ast_sip_session_media_get_transport(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+ int index;
+
+ if (!session->endpoint->media.bundle || ast_strlen_zero(session_media->mid)) {
+ return session_media;
+ }
+
+ for (index = 0; index < AST_VECTOR_SIZE(&session->pending_media_state->sessions); ++index) {
+ struct ast_sip_session_media *bundle_group_session_media;
+
+ bundle_group_session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, index);
+
+ /* The first session which is in the bundle group is considered the authoritative session for transport */
+ if (bundle_group_session_media->bundle_group == session_media->bundle_group) {
+ return bundle_group_session_media;
+ }
+ }
+
+ return session_media;
+}
+
/*!
* \brief Set an SDP stream handler for a corresponding session media.
*
@@ -207,50 +366,269 @@ static void session_media_set_handler(struct ast_sip_session_media *session_medi
session_media->handler = handler;
}
+static int stream_destroy(void *obj, void *arg, int flags)
+{
+ struct sdp_handler_list *handler_list = obj;
+ struct ast_sip_session_media *session_media = arg;
+ struct ast_sip_session_sdp_handler *handler;
+
+ AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
+ handler->stream_destroy(session_media);
+ }
+
+ return 0;
+}
+
+static void session_media_dtor(void *obj)
+{
+ struct ast_sip_session_media *session_media = obj;
+
+ /* It is possible for multiple handlers to have allocated memory on the
+ * session media (usually through a stream changing types). Therefore, we
+ * traverse all the SDP handlers and let them all call stream_destroy on
+ * the session_media
+ */
+ ao2_callback(sdp_handlers, 0, stream_destroy, session_media);
+
+ if (session_media->srtp) {
+ ast_sdp_srtp_destroy(session_media->srtp);
+ }
+
+ 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,
+ struct ast_sip_session_media_state *media_state, enum ast_media_type type, int position)
+{
+ struct ast_sip_session_media *session_media = NULL;
+
+ /* It is possible for this media state to already contain a session for the stream. If this
+ * is the case we simply return it.
+ */
+ if (position < AST_VECTOR_SIZE(&media_state->sessions)) {
+ return AST_VECTOR_GET(&media_state->sessions, position);
+ }
+
+ /* Determine if we can reuse the session media from the active media state if present */
+ if (position < AST_VECTOR_SIZE(&session->active_media_state->sessions)) {
+ session_media = AST_VECTOR_GET(&session->active_media_state->sessions, position);
+ /* A stream can never exist without an accompanying media session */
+ if (session_media->type == type) {
+ ao2_ref(session_media, +1);
+ } else {
+ session_media = NULL;
+ }
+ }
+
+ if (!session_media) {
+ /* No existing media session we can use so create a new one */
+ session_media = ao2_alloc_options(sizeof(*session_media), session_media_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);
+ if (!session_media) {
+ return NULL;
+ }
+
+ session_media->encryption = session->endpoint->media.rtp.encryption;
+ session_media->keepalive_sched_id = -1;
+ session_media->timeout_sched_id = -1;
+ session_media->type = type;
+ session_media->stream_num = position;
+
+ if (session->endpoint->media.bundle) {
+ /* This is a new stream so create a new mid based on media type and position, which makes it unique.
+ * If this is the result of an offer the mid will just end up getting replaced.
+ */
+ if (ast_asprintf(&session_media->mid, "%s-%d", ast_codec_media_type2str(type), position) < 0) {
+ ao2_ref(session_media, -1);
+ return NULL;
+ }
+ session_media->bundle_group = 0;
+ } else {
+ session_media->bundle_group = -1;
+ }
+ }
+
+ AST_VECTOR_REPLACE(&media_state->sessions, position, session_media);
+
+ /* If this stream will be active in some way and it is the first of this type then consider this the default media session to match */
+ if (!media_state->default_session[type] && ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) {
+ media_state->default_session[type] = session_media;
+ }
+
+ return session_media;
+}
+
+static int is_stream_limitation_reached(enum ast_media_type type, const struct ast_sip_endpoint *endpoint, int *type_streams)
+{
+ switch (type) {
+ case AST_MEDIA_TYPE_AUDIO:
+ return !(type_streams[type] < endpoint->media.max_audio_streams);
+ case AST_MEDIA_TYPE_VIDEO:
+ return !(type_streams[type] < endpoint->media.max_video_streams);
+ case AST_MEDIA_TYPE_IMAGE:
+ /* We don't have an option for image (T.38) streams so cap it to one. */
+ return (type_streams[type] > 0);
+ case AST_MEDIA_TYPE_UNKNOWN:
+ case AST_MEDIA_TYPE_TEXT:
+ default:
+ /* We don't want any unknown or "other" streams on our endpoint,
+ * so always just say we've reached the limit
+ */
+ return 1;
+ }
+}
+
+static int get_mid_bundle_group(const pjmedia_sdp_session *sdp, const char *mid)
+{
+ int bundle_group = 0;
+ int index;
+
+ for (index = 0; index < sdp->attr_count; ++index) {
+ pjmedia_sdp_attr *attr = sdp->attr[index];
+ char value[pj_strlen(&attr->value) + 1], *mids = value, *attr_mid;
+
+ if (pj_strcmp2(&attr->name, "group") || pj_strncmp2(&attr->value, "BUNDLE", 6)) {
+ continue;
+ }
+
+ ast_copy_pj_str(value, &attr->value, sizeof(value));
+
+ /* Skip the BUNDLE at the front */
+ mids += 7;
+
+ while ((attr_mid = strsep(&mids, " "))) {
+ if (!strcmp(attr_mid, mid)) {
+ /* The ordering of attributes determines our internal identification of the bundle group based on number,
+ * with -1 being not in a bundle group. Since this is only exposed internally for response purposes it's
+ * actually even fine if things move around.
+ */
+ return bundle_group;
+ }
+ }
+
+ bundle_group++;
+ }
+
+ return -1;
+}
+
+static int set_mid_and_bundle_group(struct ast_sip_session *session,
+ struct ast_sip_session_media *session_media,
+ const pjmedia_sdp_session *sdp,
+ const struct pjmedia_sdp_media *stream)
+{
+ pjmedia_sdp_attr *attr;
+
+ if (!session->endpoint->media.bundle) {
+ return 0;
+ }
+
+ /* By default on an incoming negotiation we assume no mid and bundle group is present */
+ ast_free(session_media->mid);
+ session_media->mid = NULL;
+ session_media->bundle_group = -1;
+ session_media->bundled = 0;
+
+ /* Grab the media identifier for the stream */
+ attr = pjmedia_sdp_media_find_attr2(stream, "mid", NULL);
+ if (!attr) {
+ return 0;
+ }
+
+ session_media->mid = ast_calloc(1, attr->value.slen + 1);
+ if (!session_media->mid) {
+ return 0;
+ }
+ ast_copy_pj_str(session_media->mid, &attr->value, attr->value.slen + 1);
+
+ /* Determine what bundle group this is part of */
+ session_media->bundle_group = get_mid_bundle_group(sdp, session_media->mid);
+
+ /* If this is actually part of a bundle group then the other side requested or accepted the bundle request */
+ session_media->bundled = session_media->bundle_group != -1;
+
+ return 0;
+}
+
static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *sdp)
{
int i;
int handled = 0;
+ int type_streams[AST_MEDIA_TYPE_END] = {0};
if (session->inv_session && session->inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
ast_log(LOG_ERROR, "Failed to handle incoming SDP. Session has been already disconnected\n");
return -1;
}
+ /* It is possible for SDP deferral to have already created a pending topology */
+ if (!session->pending_media_state->topology) {
+ session->pending_media_state->topology = ast_stream_topology_alloc();
+ if (!session->pending_media_state->topology) {
+ return -1;
+ }
+ }
+
for (i = 0; i < sdp->media_count; ++i) {
/* See if there are registered handlers for this media stream type */
char media[20];
struct ast_sip_session_sdp_handler *handler;
RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
- RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+ struct ast_sip_session_media *session_media = NULL;
int res;
+ enum ast_media_type type;
+ struct ast_stream *stream = NULL;
+ pjmedia_sdp_media *remote_stream = sdp->media[i];
/* We need a null-terminated version of the media string */
ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
+ type = ast_media_type_from_str(media);
+
+ /* See if we have an already existing stream, which can occur from SDP deferral checking */
+ if (i < ast_stream_topology_get_count(session->pending_media_state->topology)) {
+ stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);
+ }
+ if (!stream) {
+ stream = ast_stream_alloc(ast_codec_media_type2str(type), type);
+ if (!stream) {
+ return -1;
+ }
+ ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream);
+ }
- session_media = ao2_find(session->media, media, OBJ_KEY);
+ session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_media_type_from_str(media), i);
if (!session_media) {
- /* if the session_media doesn't exist, there weren't
- * any handlers at the time of its creation */
+ return -1;
+ }
+
+ /* If this stream is already declined mark it as such, or mark it as such if we've reached the limit */
+ if (!remote_stream->desc.port || is_stream_limitation_reached(type, session->endpoint, type_streams)) {
+ ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
+ ast_codec_media_type2str(type), i);
+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
+ session_media->bundle_group = -1;
+ session_media->bundled = 0;
continue;
}
+ set_mid_and_bundle_group(session, session_media, sdp, remote_stream);
+
if (session_media->handler) {
handler = session_media->handler;
ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
+ ast_codec_media_type2str(session_media->type),
session_media->handler->id);
- res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp,
- sdp->media[i]);
+ res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
if (res < 0) {
/* Catastrophic failure. Abort! */
return -1;
} else if (res > 0) {
ast_debug(1, "Media stream '%s' handled by %s\n",
- session_media->stream_type,
+ ast_codec_media_type2str(session_media->type),
session_media->handler->id);
/* Handled by this handler. Move to the next stream */
handled = 1;
+ ++type_streams[type];
continue;
}
}
@@ -265,21 +643,21 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
continue;
}
ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
+ ast_codec_media_type2str(session_media->type),
handler->id);
- res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp,
- sdp->media[i]);
+ res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
if (res < 0) {
/* Catastrophic failure. Abort! */
return -1;
}
if (res > 0) {
ast_debug(1, "Media stream '%s' handled by %s\n",
- session_media->stream_type,
+ ast_codec_media_type2str(session_media->type),
handler->id);
/* Handled by this handler. Move to the next stream */
session_media_set_handler(session_media, handler);
handled = 1;
+ ++type_streams[type];
break;
}
}
@@ -290,110 +668,167 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
return 0;
}
-struct handle_negotiated_sdp_cb {
- struct ast_sip_session *session;
- const pjmedia_sdp_session *local;
- const pjmedia_sdp_session *remote;
-};
-
-static int handle_negotiated_sdp_session_media(void *obj, void *arg, int flags)
+static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *session_media,
+ struct ast_sip_session *session, const pjmedia_sdp_session *local,
+ const pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)
{
- struct ast_sip_session_media *session_media = obj;
- struct handle_negotiated_sdp_cb *callback_data = arg;
- struct ast_sip_session *session = callback_data->session;
- const pjmedia_sdp_session *local = callback_data->local;
- const pjmedia_sdp_session *remote = callback_data->remote;
- int i;
-
- for (i = 0; i < local->media_count; ++i) {
- /* See if there are registered handlers for this media stream type */
- char media[20];
- struct ast_sip_session_sdp_handler *handler;
- RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
- int res;
+ /* See if there are registered handlers for this media stream type */
+ struct pjmedia_sdp_media *local_stream = local->media[index];
+ char media[20];
+ struct ast_sip_session_sdp_handler *handler;
+ RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
+ int res;
- if (!remote->media[i]) {
- continue;
+ /* For backwards compatibility we only reflect the stream state correctly on
+ * the non-default streams. This is because the stream state is also used for
+ * signaling that someone has placed us on hold. This situation is not handled
+ * currently and can result in the remote side being sort of placed on hold too.
+ */
+ if (!ast_sip_session_is_pending_stream_default(session, asterisk_stream)) {
+ /* Determine the state of the stream based on our local SDP */
+ if (pjmedia_sdp_media_find_attr2(local_stream, "sendonly", NULL)) {
+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDONLY);
+ } else if (pjmedia_sdp_media_find_attr2(local_stream, "recvonly", NULL)) {
+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_RECVONLY);
+ } else if (pjmedia_sdp_media_find_attr2(local_stream, "inactive", NULL)) {
+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_INACTIVE);
+ } else {
+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDRECV);
}
+ } else {
+ ast_stream_set_state(asterisk_stream, AST_STREAM_STATE_SENDRECV);
+ }
- /* We need a null-terminated version of the media string */
- ast_copy_pj_str(media, &local->media[i]->desc.media, sizeof(media));
+ /* We need a null-terminated version of the media string */
+ ast_copy_pj_str(media, &local->media[index]->desc.media, sizeof(media));
- /* stream type doesn't match the one we're looking to fill */
- if (strcasecmp(session_media->stream_type, media)) {
- continue;
- }
+ set_mid_and_bundle_group(session, session_media, remote, remote->media[index]);
- handler = session_media->handler;
- if (handler) {
- ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
+ handler = session_media->handler;
+ if (handler) {
+ ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+ ast_codec_media_type2str(session_media->type),
+ handler->id);
+ res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);
+ if (res >= 0) {
+ ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+ ast_codec_media_type2str(session_media->type),
handler->id);
- res = handler->apply_negotiated_sdp_stream(session, session_media, local,
- local->media[i], remote, remote->media[i]);
- if (res >= 0) {
- ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
- handler->id);
- return CMP_MATCH;
- }
return 0;
}
+ return -1;
+ }
- handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
- if (!handler_list) {
- ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+ handler_list = ao2_find(sdp_handlers, media, OBJ_KEY);
+ if (!handler_list) {
+ ast_debug(1, "No registered SDP handlers for media type '%s'\n", media);
+ return -1;
+ }
+ AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
+ if (handler == session_media->handler) {
continue;
}
- AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
- if (handler == session_media->handler) {
- continue;
- }
- ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
+ ast_debug(1, "Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+ ast_codec_media_type2str(session_media->type),
+ handler->id);
+ res = handler->apply_negotiated_sdp_stream(session, session_media, local, remote, index, asterisk_stream);
+ if (res < 0) {
+ /* Catastrophic failure. Abort! */
+ return -1;
+ }
+ if (res > 0) {
+ ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+ ast_codec_media_type2str(session_media->type),
handler->id);
- res = handler->apply_negotiated_sdp_stream(session, session_media, local,
- local->media[i], remote, remote->media[i]);
- if (res < 0) {
- /* Catastrophic failure. Abort! */
- return 0;
- }
- if (res > 0) {
- ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
- session_media->stream_type,
- handler->id);
- /* Handled by this handler. Move to the next stream */
- session_media_set_handler(session_media, handler);
- return CMP_MATCH;
- }
+ /* Handled by this handler. Move to the next stream */
+ session_media_set_handler(session_media, handler);
+ return 0;
}
}
if (session_media->handler && session_media->handler->stream_stop) {
ast_debug(1, "Stopping SDP media stream '%s' as it is not currently negotiated\n",
- session_media->stream_type);
+ ast_codec_media_type2str(session_media->type));
session_media->handler->stream_stop(session_media);
}
- return CMP_MATCH;
+ return 0;
}
static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_sdp_session *local, const pjmedia_sdp_session *remote)
{
- RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup);
- struct handle_negotiated_sdp_cb callback_data = {
- .session = session,
- .local = local,
- .remote = remote,
- };
+ int i;
+ struct ast_stream_topology *topology;
- successful = ao2_callback(session->media, OBJ_MULTIPLE, handle_negotiated_sdp_session_media, &callback_data);
- if (successful && ao2_iterator_count(successful) == ao2_container_count(session->media)) {
- /* Nothing experienced a catastrophic failure */
- ast_queue_frame(session->channel, &ast_null_frame);
- return 0;
+ for (i = 0; i < local->media_count; ++i) {
+ struct ast_sip_session_media *session_media;
+ struct ast_stream *stream;
+
+ if (!remote->media[i]) {
+ continue;
+ }
+
+ /* If we're handling negotiated streams, then we should already have set
+ * up session media instances (and Asterisk streams) that correspond to
+ * the local SDP, and there should be the same number of session medias
+ * and streams as there are local SDP streams
+ */
+ ast_assert(i < AST_VECTOR_SIZE(&session->pending_media_state->sessions));
+ ast_assert(i < ast_stream_topology_get_count(session->pending_media_state->topology));
+
+ session_media = AST_VECTOR_GET(&session->pending_media_state->sessions, i);
+ stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);
+
+ /* The stream state will have already been set to removed when either we
+ * negotiate the incoming SDP stream or when we produce our own local SDP.
+ * This can occur if an internal thing has requested it to be removed, or if
+ * we remove it as a result of the stream limit being reached.
+ */
+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
+ /* This stream is no longer being used so release any resources the handler
+ * may have on it.
+ */
+ if (session_media->handler) {
+ session_media_set_handler(session_media, NULL);
+ }
+ continue;
+ }
+
+ if (handle_negotiated_sdp_session_media(session_media, session, local, remote, i, stream)) {
+ return -1;
+ }
}
- return -1;
+
+ /* Apply the pending media state to the channel and make it active */
+ ast_channel_lock(session->channel);
+
+ /* Update the topology on the channel to match the accepted one */
+ topology = ast_stream_topology_clone(session->pending_media_state->topology);
+ if (topology) {
+ ast_channel_set_stream_topology(session->channel, topology);
+ }
+
+ /* Remove all current file descriptors from the channel */
+ for (i = 0; i < AST_VECTOR_SIZE(&session->active_media_state->read_callbacks); ++i) {
+ ast_channel_internal_fd_clear(session->channel, i + AST_EXTENDED_FDS);
+ }
+
+ /* Add all the file descriptors from the pending media state */
+ for (i = 0; i < AST_VECTOR_SIZE(&session->pending_media_state->read_callbacks); ++i) {
+ struct ast_sip_session_media_read_callback_state *callback_state = AST_VECTOR_GET_ADDR(&session->pending_media_state->read_callbacks, i);
+
+ ast_channel_internal_fd_set(session->channel, i + AST_EXTENDED_FDS, callback_state->fd);
+ }
+
+ /* Active and pending flip flop as needed */
+ SWAP(session->active_media_state, session->pending_media_state);
+ ast_sip_session_media_state_reset(session->pending_media_state);
+
+ ast_channel_unlock(session->channel);
+
+ ast_queue_frame(session->channel, &ast_null_frame);
+
+ return 0;
}
AST_RWLIST_HEAD_STATIC(session_supplements, ast_sip_session_supplement);
@@ -570,6 +1005,8 @@ struct ast_sip_session_delayed_request {
ast_sip_session_response_cb on_response;
/*! Whether to generate new SDP */
int generate_new_sdp;
+ /*! Requested media state for the SDP */
+ struct ast_sip_session_media_state *media_state;
AST_LIST_ENTRY(ast_sip_session_delayed_request) next;
};
@@ -578,7 +1015,8 @@ static struct ast_sip_session_delayed_request *delayed_request_alloc(
ast_sip_session_request_creation_cb on_request_creation,
ast_sip_session_sdp_creation_cb on_sdp_creation,
ast_sip_session_response_cb on_response,
- int generate_new_sdp)
+ int generate_new_sdp,
+ struct ast_sip_session_media_state *media_state)
{
struct ast_sip_session_delayed_request *delay = ast_calloc(1, sizeof(*delay));
@@ -590,9 +1028,16 @@ static struct ast_sip_session_delayed_request *delayed_request_alloc(
delay->on_sdp_creation = on_sdp_creation;
delay->on_response = on_response;
delay->generate_new_sdp = generate_new_sdp;
+ delay->media_state = media_state;
return delay;
}
+static void delayed_request_free(struct ast_sip_session_delayed_request *delay)
+{
+ ast_sip_session_media_state_free(delay->media_state);
+ ast_free(delay);
+}
+
static int send_delayed_request(struct ast_sip_session *session, struct ast_sip_session_delayed_request *delay)
{
ast_debug(3, "Endpoint '%s(%s)' sending delayed %s request.\n",
@@ -604,12 +1049,16 @@ static int send_delayed_request(struct ast_sip_session *session, struct ast_sip_
case DELAYED_METHOD_INVITE:
ast_sip_session_refresh(session, delay->on_request_creation,
delay->on_sdp_creation, delay->on_response,
- AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp);
+ AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp, delay->media_state);
+ /* Ownership of media state transitions to ast_sip_session_refresh */
+ delay->media_state = NULL;
return 0;
case DELAYED_METHOD_UPDATE:
ast_sip_session_refresh(session, delay->on_request_creation,
delay->on_sdp_creation, delay->on_response,
- AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp);
+ AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp, delay->media_state);
+ /* Ownership of media state transitions to ast_sip_session_refresh */
+ delay->media_state = NULL;
return 0;
case DELAYED_METHOD_BYE:
ast_sip_session_terminate(session, 0);
@@ -644,7 +1093,7 @@ static int invite_proceeding(void *vsession)
case DELAYED_METHOD_UPDATE:
AST_LIST_REMOVE_CURRENT(next);
res = send_delayed_request(session, delay);
- ast_free(delay);
+ delayed_request_free(delay);
found = 1;
break;
case DELAYED_METHOD_BYE:
@@ -698,7 +1147,7 @@ static int invite_terminated(void *vsession)
if (found) {
AST_LIST_REMOVE_CURRENT(next);
res = send_delayed_request(session, delay);
- ast_free(delay);
+ delayed_request_free(delay);
break;
}
}
@@ -775,12 +1224,14 @@ static int delay_request(struct ast_sip_session *session,
ast_sip_session_sdp_creation_cb on_sdp_creation,
ast_sip_session_response_cb on_response,
int generate_new_sdp,
- enum delayed_method method)
+ enum delayed_method method,
+ struct ast_sip_session_media_state *media_state)
{
struct ast_sip_session_delayed_request *delay = delayed_request_alloc(method,
- on_request, on_sdp_creation, on_response, generate_new_sdp);
+ on_request, on_sdp_creation, on_response, generate_new_sdp, media_state);
if (!delay) {
+ ast_sip_session_media_state_free(media_state);
return -1;
}
@@ -881,16 +1332,23 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
ast_sip_session_request_creation_cb on_request_creation,
ast_sip_session_sdp_creation_cb on_sdp_creation,
ast_sip_session_response_cb on_response,
- enum ast_sip_session_refresh_method method, int generate_new_sdp)
+ enum ast_sip_session_refresh_method method, int generate_new_sdp,
+ struct ast_sip_session_media_state *media_state)
{
pjsip_inv_session *inv_session = session->inv_session;
pjmedia_sdp_session *new_sdp = NULL;
pjsip_tx_data *tdata;
+ if (media_state && (!media_state->topology || !generate_new_sdp)) {
+ ast_sip_session_media_state_free(media_state);
+ return -1;
+ }
+
if (inv_session->state == PJSIP_INV_STATE_DISCONNECTED) {
/* Don't try to do anything with a hung-up call */
ast_debug(3, "Not sending reinvite to %s because of disconnected state...\n",
ast_sorcery_object_get_id(session->endpoint));
+ ast_sip_session_media_state_free(media_state);
return 0;
}
@@ -901,7 +1359,8 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
return delay_request(session, on_request_creation, on_sdp_creation, on_response,
generate_new_sdp,
method == AST_SIP_SESSION_REFRESH_METHOD_INVITE
- ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE);
+ ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE,
+ media_state);
}
if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {
@@ -910,13 +1369,14 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
ast_debug(3, "Delay sending reinvite to %s because of outstanding transaction...\n",
ast_sorcery_object_get_id(session->endpoint));
return delay_request(session, on_request_creation, on_sdp_creation,
- on_response, generate_new_sdp, DELAYED_METHOD_INVITE);
+ on_response, generate_new_sdp, DELAYED_METHOD_INVITE, media_state);
} else if (inv_session->state != PJSIP_INV_STATE_CONFIRMED) {
/* Initial INVITE transaction failed to progress us to a confirmed state
* which means re-invites are not possible
*/
ast_debug(3, "Not sending reinvite to %s because not in confirmed state...\n",
ast_sorcery_object_get_id(session->endpoint));
+ ast_sip_session_media_state_free(media_state);
return 0;
}
}
@@ -931,33 +1391,130 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
return delay_request(session, on_request_creation, on_sdp_creation,
on_response, generate_new_sdp,
method == AST_SIP_SESSION_REFRESH_METHOD_INVITE
- ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE);
+ ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, media_state);
+ }
+
+ /* If an explicitly requested media state has been provided use it instead of any pending one */
+ if (media_state) {
+ int index;
+ int type_streams[AST_MEDIA_TYPE_END] = {0};
+ struct ast_stream *stream;
+
+ /* Prune the media state so the number of streams fit within the configured limits - we do it here
+ * so that the index of the resulting streams in the SDP match. If we simply left the streams out
+ * of the SDP when producing it we'd be in trouble. We also enforce formats here for media types that
+ * are configurable on the endpoint.
+ */
+ for (index = 0; index < ast_stream_topology_get_count(media_state->topology); ++index) {
+ stream = ast_stream_topology_get_stream(media_state->topology, index);
+
+ if (is_stream_limitation_reached(ast_stream_get_type(stream), session->endpoint, type_streams)) {
+ if (index < AST_VECTOR_SIZE(&media_state->sessions)) {
+ struct ast_sip_session_media *session_media = AST_VECTOR_GET(&media_state->sessions, index);
+
+ ao2_cleanup(session_media);
+ AST_VECTOR_REMOVE(&media_state->sessions, index, 1);
+ }
+
+ ast_stream_topology_del_stream(media_state->topology, index);
+
+ /* A stream has potentially moved into our spot so we need to jump back so we process it */
+ index -= 1;
+ continue;
+ }
+
+
+ /* Enforce the configured allowed codecs on audio and video streams */
+ if (ast_stream_get_type(stream) == AST_MEDIA_TYPE_AUDIO || ast_stream_get_type(stream) == AST_MEDIA_TYPE_VIDEO) {
+ struct ast_format_cap *joint_cap;
+
+ joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!joint_cap) {
+ ast_sip_session_media_state_free(media_state);
+ return 0;
+ }
+
+ ast_format_cap_get_compatible(ast_stream_get_formats(stream), session->endpoint->media.codecs, joint_cap);
+ if (!ast_format_cap_count(joint_cap)) {
+ ao2_ref(joint_cap, -1);
+ ast_sip_session_media_state_free(media_state);
+ return 0;
+ }
+
+ ast_stream_set_formats(stream, joint_cap);
+ }
+
+ ++type_streams[ast_stream_get_type(stream)];
+ }
+
+ if (session->active_media_state->topology) {
+ /* SDP is a fun thing. Take for example the fact that streams are never removed. They just become
+ * declined. To better handle this in the case where something requests a topology change for fewer
+ * streams than are currently present we fill in the topology to match the current number of streams
+ * that are active.
+ */
+ for (index = ast_stream_topology_get_count(media_state->topology);
+ index < ast_stream_topology_get_count(session->active_media_state->topology); ++index) {
+ struct ast_stream *cloned;
+
+ stream = ast_stream_topology_get_stream(session->active_media_state->topology, index);
+ ast_assert(stream != NULL);
+
+ cloned = ast_stream_clone(stream, NULL);
+ if (!cloned) {
+ ast_sip_session_media_state_free(media_state);
+ return -1;
+ }
+
+ ast_stream_set_state(cloned, AST_STREAM_STATE_REMOVED);
+ ast_stream_topology_append_stream(media_state->topology, cloned);
+ }
+
+ /* If the resulting media state matches the existing active state don't bother doing a session refresh */
+ if (ast_stream_topology_equal(session->active_media_state->topology, media_state->topology)) {
+ ast_sip_session_media_state_free(media_state);
+ return 0;
+ }
+ }
+
+ ast_sip_session_media_state_free(session->pending_media_state);
+ session->pending_media_state = media_state;
}
new_sdp = generate_session_refresh_sdp(session);
if (!new_sdp) {
ast_log(LOG_ERROR, "Failed to generate session refresh SDP. Not sending session refresh\n");
+ ast_sip_session_media_state_reset(session->pending_media_state);
return -1;
}
if (on_sdp_creation) {
if (on_sdp_creation(session, new_sdp)) {
+ ast_sip_session_media_state_reset(session->pending_media_state);
return -1;
}
}
}
-
if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {
if (pjsip_inv_reinvite(inv_session, NULL, new_sdp, &tdata)) {
ast_log(LOG_WARNING, "Failed to create reinvite properly.\n");
+ if (generate_new_sdp) {
+ ast_sip_session_media_state_reset(session->pending_media_state);
+ }
return -1;
}
} else if (pjsip_inv_update(inv_session, NULL, new_sdp, &tdata)) {
ast_log(LOG_WARNING, "Failed to create UPDATE properly.\n");
+ if (generate_new_sdp) {
+ ast_sip_session_media_state_reset(session->pending_media_state);
+ }
return -1;
}
if (on_request_creation) {
if (on_request_creation(session, tdata)) {
+ if (generate_new_sdp) {
+ ast_sip_session_media_state_reset(session->pending_media_state);
+ }
return -1;
}
}
@@ -992,22 +1549,40 @@ static int sdp_requires_deferral(struct ast_sip_session *session, const pjmedia_
{
int i;
+ if (!session->pending_media_state->topology) {
+ session->pending_media_state->topology = ast_stream_topology_alloc();
+ if (!session->pending_media_state->topology) {
+ return -1;
+ }
+ }
+
for (i = 0; i < sdp->media_count; ++i) {
/* See if there are registered handlers for this media stream type */
char media[20];
struct ast_sip_session_sdp_handler *handler;
RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
- RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+ struct ast_stream *stream;
+ enum ast_media_type type;
+ struct ast_sip_session_media *session_media = NULL;
enum ast_sip_session_sdp_stream_defer res;
/* We need a null-terminated version of the media string */
ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
- session_media = ao2_find(session->media, media, OBJ_KEY);
+ type = ast_media_type_from_str(media);
+ stream = ast_stream_alloc(ast_codec_media_type2str(type), type);
+ if (!stream) {
+ return -1;
+ }
+
+ /* As this is only called on an incoming SDP offer before processing it is not possible
+ * for streams and their media sessions to exist.
+ */
+ ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream);
+
+ session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_media_type_from_str(media), i);
if (!session_media) {
- /* if the session_media doesn't exist, there weren't
- * any handlers at the time of its creation */
- continue;
+ return -1;
}
if (session_media->handler) {
@@ -1269,29 +1844,6 @@ static int datastore_cmp(void *obj, void *arg, int flags)
return strcmp(datastore1->uid, uid2) ? 0 : CMP_MATCH | CMP_STOP;
}
-static void session_media_dtor(void *obj)
-{
- struct ast_sip_session_media *session_media = obj;
- struct sdp_handler_list *handler_list;
- /* It is possible for SDP handlers to allocate memory on a session_media but
- * not end up getting set as the handler for this session_media. This traversal
- * ensures that all memory allocated by SDP handlers on the session_media is
- * cleared (as well as file descriptors, etc.).
- */
- handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY);
- if (handler_list) {
- struct ast_sip_session_sdp_handler *handler;
-
- AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
- handler->stream_destroy(session_media);
- }
- }
- ao2_cleanup(handler_list);
- if (session_media->srtp) {
- ast_sdp_srtp_destroy(session_media->srtp);
- }
-}
-
static void session_destructor(void *obj)
{
struct ast_sip_session *session = obj;
@@ -1320,17 +1872,17 @@ static void session_destructor(void *obj)
ast_taskprocessor_unreference(session->serializer);
ao2_cleanup(session->datastores);
- ao2_cleanup(session->media);
+ ast_sip_session_media_state_free(session->active_media_state);
+ ast_sip_session_media_state_free(session->pending_media_state);
AST_LIST_HEAD_DESTROY(&session->supplements);
while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) {
- ast_free(delay);
+ delayed_request_free(delay);
}
ast_party_id_free(&session->id);
ao2_cleanup(session->endpoint);
ao2_cleanup(session->aor);
ao2_cleanup(session->contact);
- ao2_cleanup(session->req_caps);
ao2_cleanup(session->direct_media_cap);
ast_dsp_free(session->dsp);
@@ -1357,25 +1909,6 @@ static int add_supplements(struct ast_sip_session *session)
return 0;
}
-static int add_session_media(void *obj, void *arg, int flags)
-{
- struct sdp_handler_list *handler_list = obj;
- struct ast_sip_session *session = arg;
- RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
-
- session_media = ao2_alloc(sizeof(*session_media) + strlen(handler_list->stream_type), session_media_dtor);
- if (!session_media) {
- return CMP_STOP;
- }
- session_media->encryption = session->endpoint->media.rtp.encryption;
- session_media->keepalive_sched_id = -1;
- session_media->timeout_sched_id = -1;
- /* Safe use of strcpy */
- strcpy(session_media->stream_type, handler_list->stream_type);
- ao2_link(session->media, session_media);
- return 0;
-}
-
/*! \brief Destructor for SIP channel */
static void sip_channel_destroy(void *obj)
{
@@ -1422,14 +1955,18 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint,
if (!session->direct_media_cap) {
return NULL;
}
- session->req_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
- if (!session->req_caps) {
- return NULL;
- }
session->datastores = ao2_container_alloc(DATASTORE_BUCKETS, datastore_hash, datastore_cmp);
if (!session->datastores) {
return NULL;
}
+ session->active_media_state = ast_sip_session_media_state_alloc();
+ if (!session->active_media_state) {
+ return NULL;
+ }
+ session->pending_media_state = ast_sip_session_media_state_alloc();
+ if (!session->pending_media_state) {
+ return NULL;
+ }
if (endpoint->dtmf == AST_SIP_DTMF_INBAND || endpoint->dtmf == AST_SIP_DTMF_AUTO) {
dsp_features |= DSP_FEATURE_DIGIT_DETECT;
@@ -1448,13 +1985,6 @@ struct ast_sip_session *ast_sip_session_alloc(struct ast_sip_endpoint *endpoint,
session->endpoint = ao2_bump(endpoint);
- session->media = ao2_container_alloc(MEDIA_BUCKETS, session_media_hash, session_media_cmp);
- if (!session->media) {
- return NULL;
- }
- /* fill session->media with available types */
- ao2_callback(sdp_handlers, OBJ_NODATA, add_session_media, session);
-
if (rdata) {
/*
* We must continue using the serializer that the original
@@ -1704,7 +2234,7 @@ static int setup_outbound_invite_auth(pjsip_dialog *dlg)
struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint *endpoint,
struct ast_sip_contact *contact, const char *location, const char *request_user,
- struct ast_format_cap *req_caps)
+ struct ast_stream_topology *req_topology)
{
const char *uri = NULL;
RAII_VAR(struct ast_sip_aor *, found_aor, NULL, ao2_cleanup);
@@ -1768,22 +2298,68 @@ struct ast_sip_session *ast_sip_session_create_outgoing(struct ast_sip_endpoint
session->aor = ao2_bump(found_aor);
ast_party_id_copy(&session->id, &endpoint->id.self);
- if (ast_format_cap_count(req_caps)) {
- /* get joint caps between req_caps and endpoint caps */
- struct ast_format_cap *joint_caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (ast_stream_topology_get_count(req_topology) > 0) {
+ /* get joint caps between req_topology and endpoint topology */
+ int i;
+
+ for (i = 0; i < ast_stream_topology_get_count(req_topology); ++i) {
+ struct ast_stream *req_stream;
+ struct ast_format_cap *req_cap;
+ struct ast_format_cap *joint_cap;
+ struct ast_stream *clone_stream;
+
+ req_stream = ast_stream_topology_get_stream(req_topology, i);
+
+ if (ast_stream_get_state(req_stream) == AST_STREAM_STATE_REMOVED) {
+ continue;
+ }
+
+ req_cap = ast_stream_get_formats(req_stream);
+
+ joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!joint_cap) {
+ continue;
+ }
+
+ ast_format_cap_get_compatible(req_cap, endpoint->media.codecs, joint_cap);
+ if (!ast_format_cap_count(joint_cap)) {
+ ao2_ref(joint_cap, -1);
+ continue;
+ }
+
+ clone_stream = ast_stream_clone(req_stream, NULL);
+ if (!clone_stream) {
+ ao2_ref(joint_cap, -1);
+ continue;
+ }
+
+ ast_stream_set_formats(clone_stream, joint_cap);
+ ao2_ref(joint_cap, -1);
+
+ if (!session->pending_media_state->topology) {
+ session->pending_media_state->topology = ast_stream_topology_alloc();
+ if (!session->pending_media_state->topology) {
+ pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+ ao2_ref(session, -1);
+ return NULL;
+ }
+ }
- ast_format_cap_get_compatible(req_caps, endpoint->media.codecs, joint_caps);
+ if (ast_stream_topology_append_stream(session->pending_media_state->topology, clone_stream) < 0) {
+ ast_stream_free(clone_stream);
+ continue;
+ }
+ }
+ }
- /* if joint caps */
- if (ast_format_cap_count(joint_caps)) {
- /* copy endpoint caps into session->req_caps */
- ast_format_cap_append_from_cap(session->req_caps,
- endpoint->media.codecs, AST_MEDIA_TYPE_UNKNOWN);
- /* replace instances of joint caps equivalents in session->req_caps */
- ast_format_cap_replace_from_cap(session->req_caps, joint_caps,
- AST_MEDIA_TYPE_UNKNOWN);
+ if (!session->pending_media_state->topology) {
+ /* Use the configured topology on the endpoint as the pending one */
+ session->pending_media_state->topology = ast_stream_topology_clone(endpoint->media.topology);
+ if (!session->pending_media_state->topology) {
+ pjsip_inv_terminate(inv_session, 500, PJ_FALSE);
+ ao2_ref(session, -1);
+ return NULL;
}
- ao2_cleanup(joint_caps);
}
if (pjsip_dlg_add_usage(dlg, &session_module, NULL) != PJ_SUCCESS) {
@@ -1847,7 +2423,7 @@ void ast_sip_session_terminate(struct ast_sip_session *session, int response)
/* If this is delayed the only thing that will happen is a BYE request so we don't
* actually need to store the response code for when it happens.
*/
- delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE);
+ delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE, NULL);
break;
}
/* Fall through */
@@ -1858,7 +2434,7 @@ void ast_sip_session_terminate(struct ast_sip_session *session, int response)
/* Flush any delayed requests so they cannot overlap this transaction. */
while ((delay = AST_LIST_REMOVE_HEAD(&session->delayed_requests, next))) {
- ast_free(delay);
+ delayed_request_free(delay);
}
if (packet->msg->type == PJSIP_RESPONSE_MSG) {
@@ -2387,7 +2963,7 @@ static void reschedule_reinvite(struct ast_sip_session *session, ast_sip_session
ast_debug(3, "Endpoint '%s(%s)' re-INVITE collision.\n",
ast_sorcery_object_get_id(session->endpoint),
session->channel ? ast_channel_name(session->channel) : "");
- if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE)) {
+ if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE, NULL)) {
return;
}
if (pj_timer_entry_running(&session->rescheduled_reinvite)) {
@@ -2944,27 +3520,27 @@ static void session_inv_on_tsx_state_changed(pjsip_inv_session *inv, pjsip_trans
}
}
-static int add_sdp_streams(void *obj, void *arg, void *data, int flags)
+static int add_sdp_streams(struct ast_sip_session_media *session_media,
+ struct ast_sip_session *session, pjmedia_sdp_session *answer,
+ const struct pjmedia_sdp_session *remote,
+ struct ast_stream *stream)
{
- struct ast_sip_session_media *session_media = obj;
- pjmedia_sdp_session *answer = arg;
- struct ast_sip_session *session = data;
struct ast_sip_session_sdp_handler *handler = session_media->handler;
RAII_VAR(struct sdp_handler_list *, handler_list, NULL, ao2_cleanup);
int res;
if (handler) {
/* if an already assigned handler reports a catastrophic error, fail */
- res = handler->create_outgoing_sdp_stream(session, session_media, answer);
+ res = handler->create_outgoing_sdp_stream(session, session_media, answer, remote, stream);
if (res < 0) {
- return 0;
+ return -1;
}
- return CMP_MATCH;
+ return 0;
}
- handler_list = ao2_find(sdp_handlers, session_media->stream_type, OBJ_KEY);
+ handler_list = ao2_find(sdp_handlers, ast_codec_media_type2str(session_media->type), OBJ_KEY);
if (!handler_list) {
- return CMP_MATCH;
+ return 0;
}
/* no handler for this stream type and we have a list to search */
@@ -2972,29 +3548,108 @@ static int add_sdp_streams(void *obj, void *arg, void *data, int flags)
if (handler == session_media->handler) {
continue;
}
- res = handler->create_outgoing_sdp_stream(session, session_media, answer);
+ res = handler->create_outgoing_sdp_stream(session, session_media, answer, remote, stream);
if (res < 0) {
/* catastrophic error */
- return 0;
+ return -1;
}
if (res > 0) {
/* Handled by this handler. Move to the next stream */
session_media_set_handler(session_media, handler);
- return CMP_MATCH;
+ return 0;
}
}
/* streams that weren't handled won't be included in generated outbound SDP */
- return CMP_MATCH;
+ return 0;
+}
+
+/*! \brief Bundle group building structure */
+struct sip_session_media_bundle_group {
+ /*! \brief The media identifiers in this bundle group */
+ char *mids[PJMEDIA_MAX_SDP_MEDIA];
+ /*! \brief SDP attribute string */
+ struct ast_str *attr_string;
+};
+
+static int add_bundle_groups(struct ast_sip_session *session, pj_pool_t *pool, pjmedia_sdp_session *answer)
+{
+ pj_str_t stmp;
+ pjmedia_sdp_attr *attr;
+ struct sip_session_media_bundle_group bundle_groups[PJMEDIA_MAX_SDP_MEDIA];
+ 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));
+
+ /* 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);
+
+ /* If this stream is not part of a bundle group we can't add it */
+ if (session_media->bundle_group == -1) {
+ continue;
+ }
+
+ bundle_group = &bundle_groups[session_media->bundle_group];
+
+ /* If this is the first mid then we need to allocate the attribute string and place BUNDLE in front */
+ if (!bundle_group->mids[0]) {
+ bundle_group->mids[0] = session_media->mid;
+ bundle_group->attr_string = ast_str_create(64);
+ if (!bundle_group->attr_string) {
+ continue;
+ }
+
+ ast_str_set(&bundle_group->attr_string, -1, "BUNDLE %s", session_media->mid);
+ continue;
+ }
+
+ for (mid_id = 1; mid_id < PJMEDIA_MAX_SDP_MEDIA; ++mid_id) {
+ if (!bundle_group->mids[mid_id]) {
+ bundle_group->mids[mid_id] = session_media->mid;
+ ast_str_append(&bundle_group->attr_string, -1, " %s", session_media->mid);
+ break;
+ } else if (!strcmp(bundle_group->mids[mid_id], session_media->mid)) {
+ break;
+ }
+ }
+ }
+
+ /* Add all bundle groups that have mids to the SDP */
+ for (index = 0; index < PJMEDIA_MAX_SDP_MEDIA; ++index) {
+ bundle_group = &bundle_groups[index];
+
+ if (!bundle_group->attr_string) {
+ continue;
+ }
+
+ attr = pjmedia_sdp_attr_create(pool, "group", pj_cstr(&stmp, ast_str_buffer(bundle_group->attr_string)));
+ pjmedia_sdp_attr_add(&answer->attr_count, answer->attr, attr);
+
+ ast_free(bundle_group->attr_string);
+ }
+
+ return 0;
}
static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, struct ast_sip_session *session, const pjmedia_sdp_session *offer)
{
- RAII_VAR(struct ao2_iterator *, successful, NULL, ao2_iterator_cleanup);
static const pj_str_t STR_IN = { "IN", 2 };
static const pj_str_t STR_IP4 = { "IP4", 3 };
static const pj_str_t STR_IP6 = { "IP6", 3 };
pjmedia_sdp_session *local;
+ int i;
+ int stream;
if (inv->state == PJSIP_INV_STATE_DISCONNECTED) {
ast_log(LOG_ERROR, "Failed to create session SDP. Session has been already disconnected\n");
@@ -3015,47 +3670,100 @@ static struct pjmedia_sdp_session *create_local_sdp(pjsip_inv_session *inv, stru
pj_strdup2(inv->pool_prov, &local->origin.user, session->endpoint->media.sdpowner);
pj_strdup2(inv->pool_prov, &local->name, session->endpoint->media.sdpsession);
- /* Now let the handlers add streams of various types, pjmedia will automatically reorder the media streams for us */
- successful = ao2_callback_data(session->media, OBJ_MULTIPLE, add_sdp_streams, local, session);
- if (!successful || ao2_iterator_count(successful) != ao2_container_count(session->media)) {
- /* Something experienced a catastrophic failure */
- return NULL;
+ if (!session->pending_media_state->topology || !ast_stream_topology_get_count(session->pending_media_state->topology)) {
+ /* We've encountered a situation where we have been told to create a local SDP but noone has given us any indication
+ * of what kind of stream topology they would like. As a fallback we use the topology from the configured endpoint.
+ */
+ ast_stream_topology_free(session->pending_media_state->topology);
+ session->pending_media_state->topology = ast_stream_topology_clone(session->endpoint->media.topology);
+ if (!session->pending_media_state->topology) {
+ return NULL;
+ }
}
- /* Use the connection details of the first media stream if possible for SDP level */
- if (local->media_count) {
- int stream;
+ for (i = 0; i < ast_stream_topology_get_count(session->pending_media_state->topology); ++i) {
+ struct ast_sip_session_media *session_media;
+ struct ast_stream *stream;
+ unsigned int streams = local->media_count;
- /* Since we are using the first media stream as the SDP level we can get rid of it
- * from the stream itself
- */
- local->conn = local->media[0]->conn;
- local->media[0]->conn = NULL;
- pj_strassign(&local->origin.net_type, &local->conn->net_type);
- pj_strassign(&local->origin.addr_type, &local->conn->addr_type);
- pj_strassign(&local->origin.addr, &local->conn->addr);
-
- /* Go through each media stream seeing if the connection details actually differ,
- * if not just use SDP level and reduce the SDP size
+ /* This code does not enforce any maximum stream count limitations as that is done on either
+ * the handling of an incoming SDP offer or on the handling of a session refresh.
*/
- for (stream = 1; stream < local->media_count; stream++) {
+
+ stream = ast_stream_topology_get_stream(session->pending_media_state->topology, i);
+
+ session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_stream_get_type(stream), i);
+ if (!session_media) {
+ return NULL;
+ }
+
+ if (add_sdp_streams(session_media, session, local, offer, stream)) {
+ return NULL;
+ }
+
+ /* If a stream was actually added then add any additional details */
+ if (streams != local->media_count) {
+ pjmedia_sdp_media *media = local->media[streams];
+ pj_str_t stmp;
+ pjmedia_sdp_attr *attr;
+
+ /* Add the media identifier if present */
+ if (!ast_strlen_zero(session_media->mid)) {
+ attr = pjmedia_sdp_attr_create(inv->pool_prov, "mid", pj_cstr(&stmp, session_media->mid));
+ pjmedia_sdp_attr_add(&media->attr_count, media->attr, attr);
+ }
+ }
+
+ /* Ensure that we never exceed the maximum number of streams PJMEDIA will allow. */
+ if (local->media_count == PJMEDIA_MAX_SDP_MEDIA) {
+ break;
+ }
+ }
+
+ /* Add any bundle groups that are present on the media state */
+ if (add_bundle_groups(session, inv->pool_prov, local)) {
+ return NULL;
+ }
+
+ /* Use the connection details of an available media if possible for SDP level */
+ for (stream = 0; stream < local->media_count; stream++) {
+ if (!local->media[stream]->conn) {
+ continue;
+ }
+
+ if (local->conn) {
if (!pj_strcmp(&local->conn->net_type, &local->media[stream]->conn->net_type) &&
!pj_strcmp(&local->conn->addr_type, &local->media[stream]->conn->addr_type) &&
!pj_strcmp(&local->conn->addr, &local->media[stream]->conn->addr)) {
local->media[stream]->conn = NULL;
}
+ continue;
}
- } else {
- local->origin.net_type = STR_IN;
- local->origin.addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4;
+
+ /* This stream's connection info will serve as the connection details for SDP level */
+ local->conn = local->media[stream]->conn;
+ local->media[stream]->conn = NULL;
+
+ continue;
+ }
+
+ /* If no SDP level connection details are present then create some */
+ if (!local->conn) {
+ local->conn = pj_pool_zalloc(inv->pool_prov, sizeof(struct pjmedia_sdp_conn));
+ local->conn->net_type = STR_IN;
+ local->conn->addr_type = session->endpoint->media.rtp.ipv6 ? STR_IP6 : STR_IP4;
if (!ast_strlen_zero(session->endpoint->media.address)) {
- pj_strdup2(inv->pool_prov, &local->origin.addr, session->endpoint->media.address);
+ pj_strdup2(inv->pool_prov, &local->conn->addr, session->endpoint->media.address);
} else {
- pj_strdup2(inv->pool_prov, &local->origin.addr, ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET()));
+ pj_strdup2(inv->pool_prov, &local->conn->addr, ast_sip_get_host_ip_string(session->endpoint->media.rtp.ipv6 ? pj_AF_INET6() : pj_AF_INET()));
}
}
+ pj_strassign(&local->origin.net_type, &local->conn->net_type);
+ pj_strassign(&local->origin.addr_type, &local->conn->addr_type);
+ pj_strassign(&local->origin.addr, &local->conn->addr);
+
return local;
}
diff --git a/res/res_pjsip_session.exports.in b/res/res_pjsip_session.exports.in
index fdfc5fb47..b7bd21b89 100644
--- a/res/res_pjsip_session.exports.in
+++ b/res/res_pjsip_session.exports.in
@@ -1,27 +1,7 @@
{
global:
- LINKER_SYMBOL_PREFIXast_sip_session_terminate;
- LINKER_SYMBOL_PREFIXast_sip_session_defer_termination;
- LINKER_SYMBOL_PREFIXast_sip_session_defer_termination_cancel;
- LINKER_SYMBOL_PREFIXast_sip_session_end_if_deferred;
- LINKER_SYMBOL_PREFIXast_sip_session_register_sdp_handler;
- LINKER_SYMBOL_PREFIXast_sip_session_unregister_sdp_handler;
- LINKER_SYMBOL_PREFIXast_sip_session_register_supplement;
- LINKER_SYMBOL_PREFIXast_sip_session_unregister_supplement;
- LINKER_SYMBOL_PREFIXast_sip_session_alloc_datastore;
- LINKER_SYMBOL_PREFIXast_sip_session_add_datastore;
- LINKER_SYMBOL_PREFIXast_sip_session_get_datastore;
- LINKER_SYMBOL_PREFIXast_sip_session_remove_datastore;
- LINKER_SYMBOL_PREFIXast_sip_session_get_identity;
- LINKER_SYMBOL_PREFIXast_sip_session_refresh;
- LINKER_SYMBOL_PREFIXast_sip_session_send_response;
- LINKER_SYMBOL_PREFIXast_sip_session_send_request;
- LINKER_SYMBOL_PREFIXast_sip_session_create_invite;
- LINKER_SYMBOL_PREFIXast_sip_session_create_outgoing;
- LINKER_SYMBOL_PREFIXast_sip_session_suspend;
- LINKER_SYMBOL_PREFIXast_sip_session_unsuspend;
+ LINKER_SYMBOL_PREFIXast_sip_session_*;
LINKER_SYMBOL_PREFIXast_sip_dialog_get_session;
- LINKER_SYMBOL_PREFIXast_sip_session_resume_reinvite;
LINKER_SYMBOL_PREFIXast_sip_channel_pvt_alloc;
local:
*;
diff --git a/res/res_pjsip_t38.c b/res/res_pjsip_t38.c
index bb1641a44..fbfbd0cb0 100644
--- a/res/res_pjsip_t38.c
+++ b/res/res_pjsip_t38.c
@@ -43,6 +43,8 @@
#include "asterisk/netsock2.h"
#include "asterisk/channel.h"
#include "asterisk/acl.h"
+#include "asterisk/stream.h"
+#include "asterisk/format_cache.h"
#include "asterisk/res_pjsip.h"
#include "asterisk/res_pjsip_session.h"
@@ -63,11 +65,16 @@ struct t38_state {
struct ast_control_t38_parameters their_parms;
/*! \brief Timer entry for automatically rejecting an inbound re-invite */
pj_timer_entry timer;
+ /*! Preserved media state for when T.38 ends */
+ struct ast_sip_session_media_state *media_state;
};
/*! \brief Destructor for T.38 state information */
static void t38_state_destroy(void *obj)
{
+ struct t38_state *state = obj;
+
+ ast_sip_session_media_state_free(state->media_state);
ast_free(obj);
}
@@ -195,7 +202,7 @@ static int t38_automatic_reject(void *obj)
{
RAII_VAR(struct ast_sip_session *, session, obj, ao2_cleanup);
RAII_VAR(struct ast_datastore *, datastore, ast_sip_session_get_datastore(session, "t38"), ao2_cleanup);
- RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(session->media, "image", OBJ_KEY), ao2_cleanup);
+ struct ast_sip_session_media *session_media;
if (!datastore) {
return 0;
@@ -204,6 +211,7 @@ static int t38_automatic_reject(void *obj)
ast_debug(2, "Automatically rejecting T.38 request on channel '%s'\n",
session->channel ? ast_channel_name(session->channel) : "<gone>");
+ session_media = session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
t38_change_state(session, session_media, datastore->data, T38_REJECTED);
ast_sip_session_resume_reinvite(session);
@@ -259,7 +267,6 @@ static int t38_initialize_session(struct ast_sip_session *session, struct ast_si
return -1;
}
- ast_channel_set_fd(session->channel, 5, ast_udptl_fd(session_media->udptl));
ast_udptl_set_error_correction_scheme(session_media->udptl, session->endpoint->media.t38.error_correction);
ast_udptl_setnat(session_media->udptl, session->endpoint->media.t38.nat);
ast_udptl_set_far_max_datagram(session_media->udptl, session->endpoint->media.t38.maxdatagram);
@@ -271,19 +278,15 @@ static int t38_initialize_session(struct ast_sip_session *session, struct ast_si
/*! \brief Callback for when T.38 reinvite SDP is created */
static int t38_reinvite_sdp_cb(struct ast_sip_session *session, pjmedia_sdp_session *sdp)
{
- int stream;
-
- /* Move the image media stream to the front and have it as the only stream, pjmedia will fill in
- * dummy streams for the rest
- */
- for (stream = 0; stream < sdp->media_count++; ++stream) {
- if (!pj_strcmp2(&sdp->media[stream]->desc.media, "image")) {
- sdp->media[0] = sdp->media[stream];
- sdp->media_count = 1;
- break;
- }
+ struct t38_state *state;
+
+ state = t38_state_get_or_alloc(session);
+ if (!state) {
+ return -1;
}
+ state->media_state = ast_sip_session_media_state_clone(session->active_media_state);
+
return 0;
}
@@ -292,34 +295,109 @@ static int t38_reinvite_response_cb(struct ast_sip_session *session, pjsip_rx_da
{
struct pjsip_status_line status = rdata->msg_info.msg->line.status;
struct t38_state *state;
- RAII_VAR(struct ast_sip_session_media *, session_media, NULL, ao2_cleanup);
+ struct ast_sip_session_media *session_media = NULL;
if (status.code == 100) {
return 0;
}
- if (!(state = t38_state_get_or_alloc(session)) ||
- !(session_media = ao2_find(session->media, "image", OBJ_KEY))) {
+ state = t38_state_get_or_alloc(session);
+ if (!state) {
ast_log(LOG_WARNING, "Received response to T.38 re-invite on '%s' but state unavailable\n",
ast_channel_name(session->channel));
return 0;
}
- t38_change_state(session, session_media, state, (status.code == 200) ? T38_ENABLED : T38_REJECTED);
+ if (status.code == 200) {
+ int index;
+
+ session_media = session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
+ t38_change_state(session, session_media, state, T38_ENABLED);
+
+ /* Stop all the streams in the stored away active state, they'll go back to being active once
+ * we reinvite back.
+ */
+ for (index = 0; index < AST_VECTOR_SIZE(&state->media_state->sessions); ++index) {
+ struct ast_sip_session_media *session_media = AST_VECTOR_GET(&state->media_state->sessions, index);
+
+ if (session_media && session_media->handler && session_media->handler->stream_stop) {
+ session_media->handler->stream_stop(session_media);
+ }
+ }
+ } else {
+ session_media = session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
+ t38_change_state(session, session_media, state, T38_REJECTED);
+
+ /* Abort this attempt at switching to T.38 by resetting the pending state and freeing our stored away active state */
+ ast_sip_session_media_state_free(state->media_state);
+ state->media_state = NULL;
+ ast_sip_session_media_state_reset(session->pending_media_state);
+ }
return 0;
}
+/*! \brief Helper function which creates a media state for strictly T.38 */
+static struct ast_sip_session_media_state *t38_create_media_state(struct ast_sip_session *session)
+{
+ struct ast_sip_session_media_state *media_state;
+ struct ast_stream *stream;
+ struct ast_format_cap *caps;
+ struct ast_sip_session_media *session_media;
+
+ media_state = ast_sip_session_media_state_alloc();
+ if (!media_state) {
+ return NULL;
+ }
+
+ media_state->topology = ast_stream_topology_alloc();
+ if (!media_state->topology) {
+ ast_sip_session_media_state_free(media_state);
+ return NULL;
+ }
+
+ stream = ast_stream_alloc("t38", AST_MEDIA_TYPE_IMAGE);
+ if (!stream) {
+ ast_sip_session_media_state_free(media_state);
+ return NULL;
+ }
+
+ ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV);
+ ast_stream_topology_set_stream(media_state->topology, 0, stream);
+
+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!caps) {
+ ast_sip_session_media_state_free(media_state);
+ return NULL;
+ }
+
+ ast_format_cap_append(caps, ast_format_t38, 0);
+ ast_stream_set_formats(stream, caps);
+ ao2_ref(caps, -1);
+
+ session_media = ast_sip_session_media_state_add(session, media_state, AST_MEDIA_TYPE_IMAGE, 0);
+ if (!session_media) {
+ ast_sip_session_media_state_free(media_state);
+ return NULL;
+ }
+
+ if (t38_initialize_session(session, session_media)) {
+ ast_sip_session_media_state_free(media_state);
+ return NULL;
+ }
+
+ return media_state;
+}
+
/*! \brief Task for reacting to T.38 control frame */
static int t38_interpret_parameters(void *obj)
{
RAII_VAR(struct t38_parameters_task_data *, data, obj, ao2_cleanup);
const struct ast_control_t38_parameters *parameters = data->frame->data.ptr;
struct t38_state *state = t38_state_get_or_alloc(data->session);
- RAII_VAR(struct ast_sip_session_media *, session_media, ao2_find(data->session->media, "image", OBJ_KEY), ao2_cleanup);
+ struct ast_sip_session_media *session_media = NULL;
- /* Without session media or state we can't interpret parameters */
- if (!session_media || !state) {
+ if (!state) {
return 0;
}
@@ -329,12 +407,15 @@ static int t38_interpret_parameters(void *obj)
/* Negotiation can not take place without a valid max_ifp value. */
if (!parameters->max_ifp) {
if (data->session->t38state == T38_PEER_REINVITE) {
+ session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
t38_change_state(data->session, session_media, state, T38_REJECTED);
ast_sip_session_resume_reinvite(data->session);
} else if (data->session->t38state == T38_ENABLED) {
+ session_media = data->session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
t38_change_state(data->session, session_media, state, T38_DISABLED);
ast_sip_session_refresh(data->session, NULL, NULL, NULL,
- AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);
+ AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, state->media_state);
+ state->media_state = NULL;
}
break;
} else if (data->session->t38state == T38_PEER_REINVITE) {
@@ -353,37 +434,46 @@ static int t38_interpret_parameters(void *obj)
}
state->our_parms.version = MIN(state->our_parms.version, state->their_parms.version);
state->our_parms.rate_management = state->their_parms.rate_management;
+ session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
ast_udptl_set_local_max_ifp(session_media->udptl, state->our_parms.max_ifp);
t38_change_state(data->session, session_media, state, T38_ENABLED);
ast_sip_session_resume_reinvite(data->session);
} else if ((data->session->t38state != T38_ENABLED) ||
((data->session->t38state == T38_ENABLED) &&
(parameters->request_response == AST_T38_REQUEST_NEGOTIATE))) {
- if (t38_initialize_session(data->session, session_media)) {
+ struct ast_sip_session_media_state *media_state;
+
+ media_state = t38_create_media_state(data->session);
+ if (!media_state) {
break;
}
state->our_parms = *parameters;
+ session_media = media_state->default_session[AST_MEDIA_TYPE_IMAGE];
ast_udptl_set_local_max_ifp(session_media->udptl, state->our_parms.max_ifp);
t38_change_state(data->session, session_media, state, T38_LOCAL_REINVITE);
ast_sip_session_refresh(data->session, NULL, t38_reinvite_sdp_cb, t38_reinvite_response_cb,
- AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);
+ AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, media_state);
}
break;
case AST_T38_TERMINATED:
case AST_T38_REFUSED:
case AST_T38_REQUEST_TERMINATE: /* Shutdown T38 */
if (data->session->t38state == T38_PEER_REINVITE) {
+ session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
t38_change_state(data->session, session_media, state, T38_REJECTED);
ast_sip_session_resume_reinvite(data->session);
} else if (data->session->t38state == T38_ENABLED) {
+ session_media = data->session->active_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
t38_change_state(data->session, session_media, state, T38_DISABLED);
- ast_sip_session_refresh(data->session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1);
+ ast_sip_session_refresh(data->session, NULL, NULL, NULL, AST_SIP_SESSION_REFRESH_METHOD_INVITE, 1, state->media_state);
+ state->media_state = NULL;
}
break;
case AST_T38_REQUEST_PARMS: { /* Application wants remote's parameters re-sent */
struct ast_control_t38_parameters parameters = state->their_parms;
if (data->session->t38state == T38_PEER_REINVITE) {
+ session_media = data->session->pending_media_state->default_session[AST_MEDIA_TYPE_IMAGE];
parameters.max_ifp = ast_udptl_get_far_max_ifp(session_media->udptl);
parameters.request_response = AST_T38_REQUEST_NEGOTIATE;
ast_queue_control_data(data->session->channel, AST_CONTROL_T38_PARAMETERS, &parameters, sizeof(parameters));
@@ -397,67 +487,32 @@ static int t38_interpret_parameters(void *obj)
return 0;
}
-/*! \brief Frame hook callback for writing */
-static struct ast_frame *t38_framehook_write(struct ast_channel *chan,
- struct ast_sip_session *session, struct ast_frame *f)
-{
- if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS &&
- session->endpoint->media.t38.enabled) {
- struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(session, f);
-
- if (!data) {
- return f;
- }
-
- if (ast_sip_push_task(session->serializer, t38_interpret_parameters, data)) {
- ao2_ref(data, -1);
- }
- } else if (f->frametype == AST_FRAME_MODEM) {
- struct ast_sip_session_media *session_media;
-
- /* Avoid deadlock between chan and the session->media container lock */
- ast_channel_unlock(chan);
- session_media = ao2_find(session->media, "image", OBJ_SEARCH_KEY);
- ast_channel_lock(chan);
- if (session_media && session_media->udptl) {
- ast_udptl_write(session_media->udptl, f);
- }
- ao2_cleanup(session_media);
- }
-
- return f;
-}
-
-/*! \brief Frame hook callback for reading */
-static struct ast_frame *t38_framehook_read(struct ast_channel *chan,
- struct ast_sip_session *session, struct ast_frame *f)
-{
- if (ast_channel_fdno(session->channel) == 5) {
- struct ast_sip_session_media *session_media;
-
- /* Avoid deadlock between chan and the session->media container lock */
- ast_channel_unlock(chan);
- session_media = ao2_find(session->media, "image", OBJ_SEARCH_KEY);
- ast_channel_lock(chan);
- if (session_media && session_media->udptl) {
- f = ast_udptl_read(session_media->udptl);
- }
- ao2_cleanup(session_media);
- }
-
- return f;
-}
-
/*! \brief Frame hook callback for T.38 related stuff */
static struct ast_frame *t38_framehook(struct ast_channel *chan, struct ast_frame *f,
enum ast_framehook_event event, void *data)
{
struct ast_sip_channel_pvt *channel = ast_channel_tech_pvt(chan);
- if (event == AST_FRAMEHOOK_EVENT_READ) {
- f = t38_framehook_read(chan, channel->session, f);
- } else if (event == AST_FRAMEHOOK_EVENT_WRITE) {
- f = t38_framehook_write(chan, channel->session, f);
+ if (event != AST_FRAMEHOOK_EVENT_WRITE) {
+ return f;
+ }
+
+ if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_T38_PARAMETERS) {
+ if (channel->session->endpoint->media.t38.enabled) {
+ struct t38_parameters_task_data *data = t38_parameters_task_data_alloc(channel->session, f);
+
+ if (!data) {
+ return f;
+ }
+
+ if (ast_sip_push_task(channel->session->serializer, t38_interpret_parameters, data)) {
+ ao2_ref(data, -1);
+ }
+ } else {
+ struct ast_control_t38_parameters parameters = { .request_response = AST_T38_REFUSED, };
+ ast_debug(2, "T.38 support not enabled, rejecting T.38 control packet\n");
+ ast_queue_control_data(chan, AST_CONTROL_T38_PARAMETERS, &parameters, sizeof(parameters));
+ }
}
return f;
@@ -476,7 +531,7 @@ static void t38_masq(void *data, int framehook_id,
static int t38_consume(void *data, enum ast_frame_type type)
{
- return 0;
+ return (type == AST_FRAME_CONTROL) ? 1 : 0;
}
static const struct ast_datastore_info t38_framehook_datastore = {
@@ -501,10 +556,7 @@ static void t38_attach_framehook(struct ast_sip_session *session)
return;
}
- /* Only attach the framehook if t38 is enabled for the endpoint */
- if (!session->endpoint->media.t38.enabled) {
- return;
- }
+ /* Always attach the framehook so we can quickly reject */
ast_channel_lock(session->channel);
@@ -676,11 +728,13 @@ static enum ast_sip_session_sdp_stream_defer defer_incoming_sdp_stream(
}
/*! \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 struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_media *stream)
+static int negotiate_incoming_sdp_stream(struct ast_sip_session *session,
+ struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *sdp,
+ int index, struct ast_stream *asterisk_stream)
{
struct t38_state *state;
char host[NI_MAXHOST];
+ pjmedia_sdp_media *stream = sdp->media[index];
RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
if (!session->endpoint->media.t38.enabled) {
@@ -720,7 +774,7 @@ static int negotiate_incoming_sdp_stream(struct ast_sip_session *session, struct
/*! \brief Function which creates an outgoing stream */
static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
- struct pjmedia_sdp_session *sdp)
+ struct pjmedia_sdp_session *sdp, const struct pjmedia_sdp_session *remote, struct ast_stream *stream)
{
pj_pool_t *pool = session->inv_session->pool_prov;
static const pj_str_t STR_IN = { "IN", 2 };
@@ -758,7 +812,7 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
return -1;
}
- media->desc.media = pj_str(session_media->stream_type);
+ pj_strdup2(pool, &media->desc.media, ast_codec_media_type2str(session_media->type));
media->desc.transport = STR_UDPTL;
if (ast_strlen_zero(session->endpoint->media.address)) {
@@ -826,12 +880,40 @@ static int create_outgoing_sdp_stream(struct ast_sip_session *session, struct as
return 1;
}
+static struct ast_frame *media_session_udptl_read_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media)
+{
+ struct ast_frame *frame;
+
+ if (!session_media->udptl) {
+ return &ast_null_frame;
+ }
+
+ frame = ast_udptl_read(session_media->udptl);
+ if (!frame) {
+ return NULL;
+ }
+
+ frame->stream_num = session_media->stream_num;
+
+ return frame;
+}
+
+static int media_session_udptl_write_callback(struct ast_sip_session *session, struct ast_sip_session_media *session_media, struct ast_frame *frame)
+{
+ if (!session_media->udptl) {
+ return 0;
+ }
+
+ return ast_udptl_write(session_media->udptl, frame);
+}
+
/*! \brief Function which applies a negotiated stream */
-static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct ast_sip_session_media *session_media,
- const struct pjmedia_sdp_session *local, const struct pjmedia_sdp_media *local_stream,
- const struct pjmedia_sdp_session *remote, const struct pjmedia_sdp_media *remote_stream)
+static int apply_negotiated_sdp_stream(struct ast_sip_session *session,
+ struct ast_sip_session_media *session_media, const struct pjmedia_sdp_session *local,
+ const struct pjmedia_sdp_session *remote, int index, struct ast_stream *asterisk_stream)
{
RAII_VAR(struct ast_sockaddr *, addrs, NULL, ast_free);
+ pjmedia_sdp_media *remote_stream = remote->media[index];
char host[NI_MAXHOST];
struct t38_state *state;
@@ -858,6 +940,10 @@ static int apply_negotiated_sdp_stream(struct ast_sip_session *session, struct a
t38_interpret_sdp(state, session, session_media, remote_stream);
+ ast_sip_session_media_set_write_callback(session, session_media, media_session_udptl_write_callback);
+ ast_sip_session_media_add_read_callback(session, session_media, ast_udptl_fd(session_media->udptl),
+ media_session_udptl_read_callback);
+
return 0;
}
diff --git a/res/res_pjsip_xpidf_body_generator.c b/res/res_pjsip_xpidf_body_generator.c
index 924046549..41f6224d1 100644
--- a/res/res_pjsip_xpidf_body_generator.c
+++ b/res/res_pjsip_xpidf_body_generator.c
@@ -63,7 +63,7 @@ static int xpidf_generate_body_content(void *body, void *data)
pj_xml_node *msnsubstatus;
ast_sip_presence_exten_state_to_str(state_data->exten_state, &statestring,
- &pidfstate, &pidfnote, &local_state);
+ &pidfstate, &pidfnote, &local_state, 0);
ast_sip_presence_xml_find_node_attr(state_data->pool, pres, "atom", "id",
&atom, &attr);
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index 600846c85..70561d0b6 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -68,6 +68,7 @@
#include "asterisk/module.h"
#include "asterisk/rtp_engine.h"
#include "asterisk/smoother.h"
+#include "asterisk/uuid.h"
#include "asterisk/test.h"
#define MAX_TIMESTAMP_SKEW 640
@@ -109,6 +110,8 @@
#define SRTP_MASTER_SALT_LEN 14
#define SRTP_MASTER_LEN (SRTP_MASTER_KEY_LEN + SRTP_MASTER_SALT_LEN)
+#define RTP_DTLS_ESTABLISHED -37
+
enum strict_rtp_state {
STRICT_RTP_OPEN = 0, /*! No RTP packets should be dropped, all sources accepted */
STRICT_RTP_LEARN, /*! Accept next packet as source */
@@ -238,6 +241,14 @@ struct ice_wrap {
};
#endif
+/*! \brief Structure used for mapping an incoming SSRC to an RTP instance */
+struct rtp_ssrc_mapping {
+ /*! \brief The received SSRC */
+ unsigned int ssrc;
+ /*! \brief The RTP instance this SSRC belongs to*/
+ struct ast_rtp_instance *instance;
+};
+
/*! \brief RTP session description */
struct ast_rtp {
int s;
@@ -245,6 +256,7 @@ struct ast_rtp {
struct ast_frame f;
unsigned char rawdata[8192 + AST_FRIENDLY_OFFSET];
unsigned int ssrc; /*!< Synchronization source, RFC 3550, page 10. */
+ char cname[AST_UUID_STR_LEN]; /*!< Our local CNAME */
unsigned int themssrc; /*!< Their SSRC */
unsigned int rxssrc;
unsigned int lastts;
@@ -254,7 +266,8 @@ struct ast_rtp {
unsigned int lastitexttimestamp;
unsigned int lastotexttimestamp;
unsigned int lasteventseqn;
- int lastrxseqno; /*!< Last received sequence number */
+ int lastrxseqno; /*!< Last received sequence number, from the network */
+ int expectedseqno; /*!< Next expected sequence number, from the core */
unsigned short seedrxseqno; /*!< What sequence number did they start with?*/
unsigned int seedrxts; /*!< What RTP timestamp did they start with? */
unsigned int rxcount; /*!< How many packets have we received? */
@@ -301,6 +314,11 @@ struct ast_rtp {
struct ast_rtcp *rtcp;
struct ast_rtp *bridged; /*!< Who we are Packet bridged to */
+ struct ast_rtp_instance *bundled; /*!< The RTP instance we are bundled to */
+ int stream_num; /*!< Stream num for this RTP instance */
+ AST_VECTOR(, struct rtp_ssrc_mapping) ssrc_mapping; /*!< Mappings of SSRC to RTP instances */
+ struct ast_sockaddr bind_address; /*!< Requested bind address for the sockets */
+
enum strict_rtp_state strict_rtp_state; /*!< Current state that strict RTP protection is in */
struct ast_sockaddr strict_rtp_address; /*!< Remote address information for strict RTP purposes */
@@ -317,6 +335,7 @@ struct ast_rtp {
ast_cond_t cond; /*!< ICE/TURN condition for signaling */
struct ice_wrap *ice; /*!< ao2 wrapped ICE session */
+ enum ast_rtp_ice_role role; /*!< Our role in ICE negotiation */
pj_turn_sock *turn_rtp; /*!< RTP TURN relay */
pj_turn_sock *turn_rtcp; /*!< RTCP TURN relay */
pj_turn_state_t turn_state; /*!< Current state of the TURN relay session */
@@ -476,6 +495,9 @@ static int ast_rtp_qos_set(struct ast_rtp_instance *instance, int tos, int cos,
static int ast_rtp_sendcng(struct ast_rtp_instance *instance, int level);
static unsigned int ast_rtp_get_ssrc(struct ast_rtp_instance *instance);
static const char *ast_rtp_get_cname(struct ast_rtp_instance *instance);
+static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned int ssrc);
+static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num);
+static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);
#ifdef HAVE_OPENSSL_SRTP
static int ast_rtp_activate(struct ast_rtp_instance *instance);
@@ -683,7 +705,6 @@ static void ice_wrap_dtor(void *vdoomed)
static int ice_reset_session(struct ast_rtp_instance *instance)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
- pj_ice_sess_role role = rtp->ice->real_ice->role;
int res;
ast_debug(3, "Resetting ICE for RTP instance '%p'\n", instance);
@@ -695,8 +716,9 @@ static int ice_reset_session(struct ast_rtp_instance *instance)
ast_debug(3, "Recreating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(&rtp->ice_original_rtp_addr), rtp->ice_port, instance);
res = ice_create(instance, &rtp->ice_original_rtp_addr, rtp->ice_port, 1);
if (!res) {
- /* Preserve the role that the old ICE session used */
- pj_ice_sess_change_role(rtp->ice->real_ice, role);
+ /* Use the current expected role for the ICE session */
+ pj_ice_sess_change_role(rtp->ice->real_ice, rtp->role == AST_RTP_ICE_ROLE_CONTROLLED ?
+ PJ_ICE_SESS_ROLE_CONTROLLED : PJ_ICE_SESS_ROLE_CONTROLLING);
}
/* If we only have one component now, and we previously set up TURN for RTCP,
@@ -767,6 +789,8 @@ static void ast_rtp_ice_start(struct ast_rtp_instance *instance)
ast_debug(3, "Proposed == active candidates for RTP instance '%p'\n", instance);
ao2_cleanup(rtp->ice_proposed_remote_candidates);
rtp->ice_proposed_remote_candidates = NULL;
+ /* If this ICE session is being preserved then go back to the role it currently is */
+ rtp->role = rtp->ice->real_ice->role;
return;
}
@@ -940,10 +964,7 @@ static void ast_rtp_ice_set_role(struct ast_rtp_instance *instance, enum ast_rtp
return;
}
- pj_thread_register_check();
-
- pj_ice_sess_change_role(rtp->ice->real_ice, role == AST_RTP_ICE_ROLE_CONTROLLED ?
- PJ_ICE_SESS_ROLE_CONTROLLED : PJ_ICE_SESS_ROLE_CONTROLLING);
+ rtp->role = role;
}
/*! \pre instance is locked */
@@ -1293,6 +1314,8 @@ static void ast_rtp_ice_turn_request(struct ast_rtp_instance *instance, enum ast
pj_turn_session_info info;
struct ast_sockaddr local, loop;
pj_status_t status;
+ pj_turn_sock_cfg turn_sock_cfg;
+ struct ice_wrap *ice;
ast_rtp_instance_get_local_address(instance, &local);
if (ast_sockaddr_is_ipv4(&local)) {
@@ -1355,11 +1378,20 @@ static void ast_rtp_ice_turn_request(struct ast_rtp_instance *instance, enum ast
pj_stun_config_init(&stun_config, &cachingpool.factory, 0, rtp->ioqueue->ioqueue, rtp->ioqueue->timerheap);
+ /* Use ICE session group lock for TURN session to avoid deadlock */
+ pj_turn_sock_cfg_default(&turn_sock_cfg);
+ ice = rtp->ice;
+ if (ice) {
+ turn_sock_cfg.grp_lock = ice->real_ice->grp_lock;
+ ao2_ref(ice, +1);
+ }
+
/* Release the instance lock to avoid deadlock with PJPROJECT group lock */
ao2_unlock(instance);
status = pj_turn_sock_create(&stun_config,
ast_sockaddr_is_ipv4(&addr) ? pj_AF_INET() : pj_AF_INET6(), conn_type,
- turn_cb, NULL, instance, turn_sock);
+ turn_cb, &turn_sock_cfg, instance, turn_sock);
+ ao2_cleanup(ice);
if (status != PJ_SUCCESS) {
ast_log(LOG_WARNING, "Could not create a TURN client socket\n");
ao2_lock(instance);
@@ -1907,6 +1939,9 @@ static struct ast_rtp_engine asterisk_rtp_engine = {
#endif
.ssrc_get = ast_rtp_get_ssrc,
.cname_get = ast_rtp_get_cname,
+ .set_remote_ssrc = ast_rtp_set_remote_ssrc,
+ .set_stream_num = ast_rtp_set_stream_num,
+ .bundle = ast_rtp_bundle,
};
#ifdef HAVE_OPENSSL_SRTP
@@ -1943,6 +1978,23 @@ static void dtls_perform_handshake(struct ast_rtp_instance *instance, struct dtl
}
#endif
+#ifdef HAVE_OPENSSL_SRTP
+static void dtls_perform_setup(struct dtls_details *dtls)
+{
+ if (!dtls->ssl || !SSL_is_init_finished(dtls->ssl)) {
+ return;
+ }
+
+ SSL_clear(dtls->ssl);
+ if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) {
+ SSL_set_accept_state(dtls->ssl);
+ } else {
+ SSL_set_connect_state(dtls->ssl);
+ }
+ dtls->connection = AST_RTP_DTLS_CONNECTION_NEW;
+}
+#endif
+
#ifdef HAVE_PJPROJECT
static void rtp_learning_seq_init(struct rtp_learning_info *info, uint16_t seq);
@@ -1971,9 +2023,12 @@ static void ast_rtp_on_ice_complete(pj_ice_sess *ice, pj_status_t status)
}
#ifdef HAVE_OPENSSL_SRTP
+
+ dtls_perform_setup(&rtp->dtls);
dtls_perform_handshake(instance, &rtp->dtls, 0);
if (rtp->rtcp && rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) {
+ dtls_perform_setup(&rtp->rtcp->dtls);
dtls_perform_handshake(instance, &rtp->rtcp->dtls, 1);
}
#endif
@@ -2241,59 +2296,14 @@ static int dtls_srtp_renegotiate(const void *data)
return 0;
}
-static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct ast_rtp_instance *instance, int rtcp)
+static int dtls_srtp_add_local_ssrc(struct ast_rtp *rtp, struct ast_srtp *srtp, struct ast_rtp_instance *instance, int rtcp, unsigned int ssrc, int set_remote_policy)
{
unsigned char material[SRTP_MASTER_LEN * 2];
unsigned char *local_key, *local_salt, *remote_key, *remote_salt;
struct ast_srtp_policy *local_policy, *remote_policy = NULL;
- struct ast_rtp_instance_stats stats = { 0, };
int res = -1;
struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls;
- /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */
- if (rtp->dtls_verify & AST_RTP_DTLS_VERIFY_FINGERPRINT) {
- X509 *certificate;
-
- if (!(certificate = SSL_get_peer_certificate(dtls->ssl))) {
- ast_log(LOG_WARNING, "No certificate was provided by the peer on RTP instance '%p'\n", instance);
- return -1;
- }
-
- /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */
- if (rtp->remote_fingerprint[0]) {
- const EVP_MD *type;
- unsigned char fingerprint[EVP_MAX_MD_SIZE];
- unsigned int size;
-
- if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA1) {
- type = EVP_sha1();
- } else if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA256) {
- type = EVP_sha256();
- } else {
- ast_log(LOG_WARNING, "Unsupported fingerprint hash type on RTP instance '%p'\n", instance);
- return -1;
- }
-
- if (!X509_digest(certificate, type, fingerprint, &size) ||
- !size ||
- memcmp(fingerprint, rtp->remote_fingerprint, size)) {
- X509_free(certificate);
- ast_log(LOG_WARNING, "Fingerprint provided by remote party does not match that of peer certificate on RTP instance '%p'\n",
- instance);
- return -1;
- }
- }
-
- X509_free(certificate);
- }
-
- /* Ensure that certificate verification was successful */
- if ((rtp->dtls_verify & AST_RTP_DTLS_VERIFY_CERTIFICATE) && SSL_get_verify_result(dtls->ssl) != X509_V_OK) {
- ast_log(LOG_WARNING, "Peer certificate on RTP instance '%p' failed verification test\n",
- instance);
- return -1;
- }
-
/* Produce key information and set up SRTP */
if (!SSL_export_keying_material(dtls->ssl, material, SRTP_MASTER_LEN * 2, "EXTRACTOR-dtls_srtp", 19, NULL, 0, 0)) {
ast_log(LOG_WARNING, "Unable to extract SRTP keying material from DTLS-SRTP negotiation on RTP instance '%p'\n",
@@ -2328,41 +2338,31 @@ static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct as
goto error;
}
- if (ast_rtp_instance_get_stats(instance, &stats, AST_RTP_INSTANCE_STAT_LOCAL_SSRC)) {
- goto error;
- }
+ res_srtp_policy->set_ssrc(local_policy, ssrc, 0);
- res_srtp_policy->set_ssrc(local_policy, stats.local_ssrc, 0);
+ if (set_remote_policy) {
+ if (!(remote_policy = res_srtp_policy->alloc())) {
+ goto error;
+ }
- if (!(remote_policy = res_srtp_policy->alloc())) {
- goto error;
- }
+ if (res_srtp_policy->set_master_key(remote_policy, remote_key, SRTP_MASTER_KEY_LEN, remote_salt, SRTP_MASTER_SALT_LEN) < 0) {
+ ast_log(LOG_WARNING, "Could not set key/salt information on remote policy of '%p' when setting up DTLS-SRTP\n", rtp);
+ goto error;
+ }
- if (res_srtp_policy->set_master_key(remote_policy, remote_key, SRTP_MASTER_KEY_LEN, remote_salt, SRTP_MASTER_SALT_LEN) < 0) {
- ast_log(LOG_WARNING, "Could not set key/salt information on remote policy of '%p' when setting up DTLS-SRTP\n", rtp);
- goto error;
- }
+ if (res_srtp_policy->set_suite(remote_policy, rtp->suite)) {
+ ast_log(LOG_WARNING, "Could not set suite to '%u' on remote policy of '%p' when setting up DTLS-SRTP\n", rtp->suite, rtp);
+ goto error;
+ }
- if (res_srtp_policy->set_suite(remote_policy, rtp->suite)) {
- ast_log(LOG_WARNING, "Could not set suite to '%u' on remote policy of '%p' when setting up DTLS-SRTP\n", rtp->suite, rtp);
- goto error;
+ res_srtp_policy->set_ssrc(remote_policy, 0, 1);
}
- res_srtp_policy->set_ssrc(remote_policy, 0, 1);
-
if (ast_rtp_instance_add_srtp_policy(instance, remote_policy, local_policy, rtcp)) {
ast_log(LOG_WARNING, "Could not set policies when setting up DTLS-SRTP on '%p'\n", rtp);
goto error;
}
- if (rtp->rekey) {
- ao2_ref(instance, +1);
- if ((rtp->rekeyid = ast_sched_add(rtp->sched, rtp->rekey * 1000, dtls_srtp_renegotiate, instance)) < 0) {
- ao2_ref(instance, -1);
- goto error;
- }
- }
-
res = 0;
error:
@@ -2375,6 +2375,71 @@ error:
return res;
}
+
+static int dtls_srtp_setup(struct ast_rtp *rtp, struct ast_srtp *srtp, struct ast_rtp_instance *instance, int rtcp)
+{
+ struct dtls_details *dtls = !rtcp ? &rtp->dtls : &rtp->rtcp->dtls;
+ int index;
+
+ /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */
+ if (rtp->dtls_verify & AST_RTP_DTLS_VERIFY_FINGERPRINT) {
+ X509 *certificate;
+
+ if (!(certificate = SSL_get_peer_certificate(dtls->ssl))) {
+ ast_log(LOG_WARNING, "No certificate was provided by the peer on RTP instance '%p'\n", instance);
+ return -1;
+ }
+
+ /* If a fingerprint is present in the SDP make sure that the peer certificate matches it */
+ if (rtp->remote_fingerprint[0]) {
+ const EVP_MD *type;
+ unsigned char fingerprint[EVP_MAX_MD_SIZE];
+ unsigned int size;
+
+ if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA1) {
+ type = EVP_sha1();
+ } else if (rtp->remote_hash == AST_RTP_DTLS_HASH_SHA256) {
+ type = EVP_sha256();
+ } else {
+ ast_log(LOG_WARNING, "Unsupported fingerprint hash type on RTP instance '%p'\n", instance);
+ return -1;
+ }
+
+ if (!X509_digest(certificate, type, fingerprint, &size) ||
+ !size ||
+ memcmp(fingerprint, rtp->remote_fingerprint, size)) {
+ X509_free(certificate);
+ ast_log(LOG_WARNING, "Fingerprint provided by remote party does not match that of peer certificate on RTP instance '%p'\n",
+ instance);
+ return -1;
+ }
+ }
+
+ X509_free(certificate);
+ }
+
+ if (dtls_srtp_add_local_ssrc(rtp, srtp, instance, rtcp, ast_rtp_instance_get_ssrc(instance), 1)) {
+ return -1;
+ }
+
+ for (index = 0; index < AST_VECTOR_SIZE(&rtp->ssrc_mapping); ++index) {
+ struct rtp_ssrc_mapping *mapping = AST_VECTOR_GET_ADDR(&rtp->ssrc_mapping, index);
+
+ if (dtls_srtp_add_local_ssrc(rtp, srtp, instance, rtcp, ast_rtp_instance_get_ssrc(mapping->instance), 0)) {
+ return -1;
+ }
+ }
+
+ if (rtp->rekey) {
+ ao2_ref(instance, +1);
+ if ((rtp->rekeyid = ast_sched_add(rtp->sched, rtp->rekey * 1000, dtls_srtp_renegotiate, instance)) < 0) {
+ ao2_ref(instance, -1);
+ return -1;
+ }
+ }
+
+ return 0;
+}
#endif
static int rtcp_mux(struct ast_rtp *rtp, const unsigned char *packet)
@@ -2477,7 +2542,11 @@ static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t s
/* Any further connections will be existing since this is now established */
dtls->connection = AST_RTP_DTLS_CONNECTION_EXISTING;
/* Use the keying material to set up key/salt information */
- res = dtls_srtp_setup(rtp, srtp, instance, rtcp);
+ if ((res = dtls_srtp_setup(rtp, srtp, instance, rtcp))) {
+ return res;
+ }
+ /* Notify that dtls has been established */
+ res = RTP_DTLS_ESTABLISHED;
} else {
/* Since we've sent additional traffic start the timeout timer for retransmission */
dtls_srtp_start_timeout_timer(instance, rtp, rtcp);
@@ -2526,6 +2595,17 @@ static int __rtp_recvfrom(struct ast_rtp_instance *instance, void *buf, size_t s
return -1;
}
if (!rtp->passthrough) {
+ /* If a unidirectional ICE negotiation occurs then lock on to the source of the
+ * ICE traffic and use it as the target. This will occur if the remote side only
+ * wants to receive media but never send to us.
+ */
+ if (!rtp->ice_active_remote_candidates && !rtp->ice_proposed_remote_candidates) {
+ if (rtcp) {
+ ast_sockaddr_copy(&rtp->rtcp->them, sa);
+ } else {
+ ast_rtp_instance_set_remote_address(instance, sa);
+ }
+ }
return 0;
}
rtp->passthrough = 0;
@@ -2558,7 +2638,9 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz
int len = size;
void *temp = buf;
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
- struct ast_srtp *srtp = ast_rtp_instance_get_srtp(instance, rtcp);
+ struct ast_rtp_instance *transport = rtp->bundled ? rtp->bundled : instance;
+ struct ast_rtp *transport_rtp = ast_rtp_instance_get_data(transport);
+ struct ast_srtp *srtp = ast_rtp_instance_get_srtp(transport, rtcp);
int res;
*via_ice = 0;
@@ -2568,20 +2650,29 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz
}
#ifdef HAVE_PJPROJECT
- if (rtp->ice) {
+ 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 */
- ice = rtp->ice;
+ ice = transport_rtp->ice;
ao2_ref(ice, +1);
- 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);
+ if (instance == transport) {
+ ao2_unlock(instance);
+ }
+ status = pj_ice_sess_send_data(ice->real_ice, component, temp, len);
ao2_ref(ice, -1);
- ao2_lock(instance);
+ if (instance == transport) {
+ ao2_lock(instance);
+ }
if (status == PJ_SUCCESS) {
*via_ice = 1;
return len;
@@ -2589,7 +2680,7 @@ static int __rtp_sendto(struct ast_rtp_instance *instance, void *buf, size_t siz
}
#endif
- res = ast_sendto(rtcp ? rtp->rtcp->s : rtp->s, temp, len, flags, sa);
+ res = ast_sendto(rtcp ? transport_rtp->rtcp->s : transport_rtp->s, temp, len, flags, sa);
if (res > 0) {
ast_rtp_instance_set_last_tx(instance, time(NULL));
}
@@ -2979,22 +3070,10 @@ static int ice_create(struct ast_rtp_instance *instance, struct ast_sockaddr *ad
}
#endif
-/*! \pre instance is locked */
-static int ast_rtp_new(struct ast_rtp_instance *instance,
- struct ast_sched_context *sched, struct ast_sockaddr *addr,
- void *data)
+static int rtp_allocate_transport(struct ast_rtp_instance *instance, struct ast_rtp *rtp)
{
- struct ast_rtp *rtp = NULL;
int x, startplace;
- /* Create a new RTP structure to hold all of our data */
- if (!(rtp = ast_calloc(1, sizeof(*rtp)))) {
- return -1;
- }
-
- /* Set default parameters on the newly created RTP structure */
- rtp->ssrc = ast_random();
- rtp->seqno = ast_random() & 0x7fff;
rtp->strict_rtp_state = (strictrtp ? STRICT_RTP_LEARN : STRICT_RTP_OPEN);
if (strictrtp) {
rtp_learning_seq_init(&rtp->rtp_source_learn, (uint16_t)rtp->seqno);
@@ -3004,10 +3083,9 @@ static int ast_rtp_new(struct ast_rtp_instance *instance,
/* Create a new socket for us to listen on and use */
if ((rtp->s =
create_new_socket("RTP",
- ast_sockaddr_is_ipv4(addr) ? AF_INET :
- ast_sockaddr_is_ipv6(addr) ? AF_INET6 : -1)) < 0) {
+ ast_sockaddr_is_ipv4(&rtp->bind_address) ? AF_INET :
+ ast_sockaddr_is_ipv6(&rtp->bind_address) ? AF_INET6 : -1)) < 0) {
ast_log(LOG_WARNING, "Failed to create a new socket for RTP instance '%p'\n", instance);
- ast_free(rtp);
return -1;
}
@@ -3017,11 +3095,11 @@ static int ast_rtp_new(struct ast_rtp_instance *instance,
startplace = x;
for (;;) {
- ast_sockaddr_set_port(addr, x);
+ ast_sockaddr_set_port(&rtp->bind_address, x);
/* Try to bind, this will tell us whether the port is available or not */
- if (!ast_bind(rtp->s, addr)) {
+ if (!ast_bind(rtp->s, &rtp->bind_address)) {
ast_debug(1, "Allocated port %d for RTP instance '%p'\n", x, instance);
- ast_rtp_instance_set_local_address(instance, addr);
+ ast_rtp_instance_set_local_address(instance, &rtp->bind_address);
break;
}
@@ -3034,7 +3112,6 @@ static int ast_rtp_new(struct ast_rtp_instance *instance,
if (x == startplace || (errno != EADDRINUSE && errno != EACCES)) {
ast_log(LOG_ERROR, "Oh dear... we couldn't allocate a port for RTP instance '%p'\n", instance);
close(rtp->s);
- ast_free(rtp);
return -1;
}
}
@@ -3045,40 +3122,30 @@ static int ast_rtp_new(struct ast_rtp_instance *instance,
generate_random_string(rtp->local_ufrag, sizeof(rtp->local_ufrag));
generate_random_string(rtp->local_passwd, sizeof(rtp->local_passwd));
-#endif
- ast_rtp_instance_set_data(instance, rtp);
-#ifdef HAVE_PJPROJECT
+
/* Create an ICE session for ICE negotiation */
if (icesupport) {
rtp->ice_num_components = 2;
- ast_debug(3, "Creating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(addr), x, instance);
- if (ice_create(instance, addr, x, 0)) {
+ ast_debug(3, "Creating ICE session %s (%d) for RTP instance '%p'\n", ast_sockaddr_stringify(&rtp->bind_address), x, instance);
+ if (ice_create(instance, &rtp->bind_address, x, 0)) {
ast_log(LOG_NOTICE, "Failed to create ICE session\n");
} else {
rtp->ice_port = x;
- ast_sockaddr_copy(&rtp->ice_original_rtp_addr, addr);
+ ast_sockaddr_copy(&rtp->ice_original_rtp_addr, &rtp->bind_address);
}
}
#endif
- /* Record any information we may need */
- rtp->sched = sched;
#ifdef HAVE_OPENSSL_SRTP
rtp->rekeyid = -1;
rtp->dtls.timeout_timer = -1;
#endif
- rtp->f.subclass.format = ao2_bump(ast_format_none);
- rtp->lastrxformat = ao2_bump(ast_format_none);
- rtp->lasttxformat = ao2_bump(ast_format_none);
-
return 0;
}
-/*! \pre instance is locked */
-static int ast_rtp_destroy(struct ast_rtp_instance *instance)
+static void rtp_deallocate_transport(struct ast_rtp_instance *instance, struct ast_rtp *rtp)
{
- struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
#ifdef HAVE_PJPROJECT
struct timeval wait = ast_tvadd(ast_tvnow(), ast_samp2tv(TURN_STATE_WAIT_TIME, 1000));
struct timespec ts = { .tv_sec = wait.tv_sec, .tv_nsec = wait.tv_usec * 1000, };
@@ -3088,35 +3155,16 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
ast_rtp_dtls_stop(instance);
#endif
- /* Destroy the smoother that was smoothing out audio if present */
- if (rtp->smoother) {
- ast_smoother_free(rtp->smoother);
- }
-
/* Close our own socket so we no longer get packets */
if (rtp->s > -1) {
close(rtp->s);
+ rtp->s = -1;
}
/* Destroy RTCP if it was being used */
- if (rtp->rtcp) {
- /*
- * It is not possible for there to be an active RTCP scheduler
- * entry at this point since it holds a reference to the
- * RTP instance while it's active.
- */
+ if (rtp->rtcp && rtp->rtcp->s > -1) {
close(rtp->rtcp->s);
- ast_free(rtp->rtcp->local_addr_str);
- ast_free(rtp->rtcp);
- }
-
- /* Destroy RED if it was being used */
- if (rtp->red) {
- ao2_unlock(instance);
- AST_SCHED_DEL(rtp->sched, rtp->red->schedid);
- ao2_lock(instance);
- ast_free(rtp->red);
- rtp->red = NULL;
+ rtp->rtcp->s = -1;
}
#ifdef HAVE_PJPROJECT
@@ -3137,6 +3185,7 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
while (rtp->turn_state != PJ_TURN_STATE_DESTROYING) {
ast_cond_timedwait(&rtp->cond, ao2_object_get_lockaddr(instance), &ts);
}
+ rtp->turn_rtp = NULL;
}
/* Destroy the RTCP TURN relay if being used */
@@ -3150,6 +3199,7 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
while (rtp->turn_state != PJ_TURN_STATE_DESTROYING) {
ast_cond_timedwait(&rtp->cond, ao2_object_get_lockaddr(instance), &ts);
}
+ rtp->turn_rtcp = NULL;
}
/* Destroy any ICE session */
@@ -3158,10 +3208,12 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
/* Destroy any candidates */
if (rtp->ice_local_candidates) {
ao2_ref(rtp->ice_local_candidates, -1);
+ rtp->ice_local_candidates = NULL;
}
if (rtp->ice_active_remote_candidates) {
ao2_ref(rtp->ice_active_remote_candidates, -1);
+ rtp->ice_active_remote_candidates = NULL;
}
if (rtp->ioqueue) {
@@ -3173,17 +3225,110 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
ao2_unlock(instance);
rtp_ioqueue_thread_remove(rtp->ioqueue);
ao2_lock(instance);
+ rtp->ioqueue = NULL;
}
#endif
+}
+
+/*! \pre instance is locked */
+static int ast_rtp_new(struct ast_rtp_instance *instance,
+ struct ast_sched_context *sched, struct ast_sockaddr *addr,
+ void *data)
+{
+ struct ast_rtp *rtp = NULL;
+
+ /* Create a new RTP structure to hold all of our data */
+ if (!(rtp = ast_calloc(1, sizeof(*rtp)))) {
+ return -1;
+ }
+
+ /* Set default parameters on the newly created RTP structure */
+ rtp->ssrc = ast_random();
+ ast_uuid_generate_str(rtp->cname, sizeof(rtp->cname));
+ rtp->seqno = ast_random() & 0x7fff;
+ rtp->expectedseqno = -1;
+ rtp->sched = sched;
+ ast_sockaddr_copy(&rtp->bind_address, addr);
+
+ /* Transport creation operations can grab the RTP data from the instance, so set it */
+ ast_rtp_instance_set_data(instance, rtp);
+
+ if (rtp_allocate_transport(instance, rtp)) {
+ ast_free(rtp);
+ return -1;
+ }
+
+ rtp->f.subclass.format = ao2_bump(ast_format_none);
+ rtp->lastrxformat = ao2_bump(ast_format_none);
+ rtp->lasttxformat = ao2_bump(ast_format_none);
+ rtp->stream_num = -1;
+ AST_VECTOR_INIT(&rtp->ssrc_mapping, 1);
+
+ return 0;
+}
+
+/*!
+ * \brief SSRC mapping comparator for AST_VECTOR_REMOVE_CMP_UNORDERED()
+ *
+ * \param elem Element to compare against
+ * \param value Value to compare with the vector element.
+ *
+ * \return 0 if element does not match.
+ * \return Non-zero if element matches.
+ */
+#define SSRC_MAPPING_ELEM_CMP(elem, value) (elem.instance == value)
+
+/*! \pre instance is locked */
+static int ast_rtp_destroy(struct ast_rtp_instance *instance)
+{
+ struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+
+ if (rtp->bundled) {
+ struct ast_rtp *bundled_rtp;
+
+ /* We can't hold our instance lock while removing ourselves from the parent */
+ ao2_unlock(instance);
+
+ ao2_lock(rtp->bundled);
+ bundled_rtp = ast_rtp_instance_get_data(rtp->bundled);
+ AST_VECTOR_REMOVE_CMP_UNORDERED(&bundled_rtp->ssrc_mapping, instance, SSRC_MAPPING_ELEM_CMP, AST_VECTOR_ELEM_CLEANUP_NOOP);
+ ao2_unlock(rtp->bundled);
+
+ ao2_lock(instance);
+ ao2_ref(rtp->bundled, -1);
+ }
+
+ rtp_deallocate_transport(instance, rtp);
+
+ /* Destroy the smoother that was smoothing out audio if present */
+ if (rtp->smoother) {
+ ast_smoother_free(rtp->smoother);
+ }
+
+ /* Destroy RTCP if it was being used */
+ if (rtp->rtcp) {
+ /*
+ * It is not possible for there to be an active RTCP scheduler
+ * entry at this point since it holds a reference to the
+ * RTP instance while it's active.
+ */
+ ast_free(rtp->rtcp->local_addr_str);
+ ast_free(rtp->rtcp);
+ }
+
+ /* Destroy RED if it was being used */
+ if (rtp->red) {
+ ao2_unlock(instance);
+ AST_SCHED_DEL(rtp->sched, rtp->red->schedid);
+ ao2_lock(instance);
+ ast_free(rtp->red);
+ rtp->red = NULL;
+ }
ao2_cleanup(rtp->lasttxformat);
ao2_cleanup(rtp->lastrxformat);
ao2_cleanup(rtp->f.subclass.format);
-
-#ifdef HAVE_PJPROJECT
- /* Destroy synchronization items */
- ast_cond_destroy(&rtp->cond);
-#endif
+ AST_VECTOR_FREE(&rtp->ssrc_mapping);
/* Finally destroy ourselves */
ast_free(rtp);
@@ -3433,21 +3578,18 @@ static void ast_rtp_change_source(struct ast_rtp_instance *instance)
struct ast_srtp *rtcp_srtp = ast_rtp_instance_get_srtp(instance, 1);
unsigned int ssrc = ast_random();
- if (!rtp->lastts) {
- ast_debug(3, "Not changing SSRC since we haven't sent any RTP yet\n");
- return;
- }
-
- /* We simply set this bit so that the next packet sent will have the marker bit turned on */
- ast_set_flag(rtp, FLAG_NEED_MARKER_BIT);
+ if (rtp->lastts) {
+ /* We simply set this bit so that the next packet sent will have the marker bit turned on */
+ ast_set_flag(rtp, FLAG_NEED_MARKER_BIT);
- ast_debug(3, "Changing ssrc from %u to %u due to a source change\n", rtp->ssrc, ssrc);
+ ast_debug(3, "Changing ssrc from %u to %u due to a source change\n", rtp->ssrc, ssrc);
- if (srtp) {
- ast_debug(3, "Changing ssrc for SRTP from %u to %u\n", rtp->ssrc, ssrc);
- res_srtp->change_source(srtp, rtp->ssrc, ssrc);
- if (rtcp_srtp != srtp) {
- res_srtp->change_source(rtcp_srtp, rtp->ssrc, ssrc);
+ if (srtp) {
+ ast_debug(3, "Changing ssrc for SRTP from %u to %u\n", rtp->ssrc, ssrc);
+ res_srtp->change_source(srtp, rtp->ssrc, ssrc);
+ if (rtcp_srtp != srtp) {
+ res_srtp->change_source(rtcp_srtp, rtp->ssrc, ssrc);
+ }
}
}
@@ -3562,14 +3704,13 @@ static int ast_rtcp_write_report(struct ast_rtp_instance *instance, int sr)
struct timeval now;
unsigned int now_lsw;
unsigned int now_msw;
- unsigned int *rtcpheader;
+ unsigned char *rtcpheader;
unsigned int lost_packets;
int fraction_lost;
struct timeval dlsr = { 0, };
- char bdata[512];
+ unsigned char bdata[512] = "";
int rate = rtp_get_rate(rtp->f.subclass.format);
int ice;
- int header_offset = 0;
struct ast_sockaddr remote_address = { { 0, } };
struct ast_rtp_rtcp_report_block *report_block = NULL;
RAII_VAR(struct ast_rtp_rtcp_report *, rtcp_report,
@@ -3623,38 +3764,42 @@ static int ast_rtcp_write_report(struct ast_rtp_instance *instance, int sr)
}
}
timeval2ntp(rtcp_report->sender_information.ntp_timestamp, &now_msw, &now_lsw);
- rtcpheader = (unsigned int *)bdata;
- rtcpheader[1] = htonl(rtcp_report->ssrc); /* Our SSRC */
+ rtcpheader = bdata;
+ put_unaligned_uint32(rtcpheader + 4, htonl(rtcp_report->ssrc)); /* Our SSRC */
len += 8;
if (sr) {
- header_offset = 5;
- rtcpheader[2] = htonl(now_msw); /* now, MSW. gettimeofday() + SEC_BETWEEN_1900_AND_1970*/
- rtcpheader[3] = htonl(now_lsw); /* now, LSW */
- rtcpheader[4] = htonl(rtcp_report->sender_information.rtp_timestamp);
- rtcpheader[5] = htonl(rtcp_report->sender_information.packet_count);
- rtcpheader[6] = htonl(rtcp_report->sender_information.octet_count);
+ put_unaligned_uint32(rtcpheader + len, htonl(now_msw)); /* now, MSW. gettimeofday() + SEC_BETWEEN_1900_AND_1970*/
+ put_unaligned_uint32(rtcpheader + len + 4, htonl(now_lsw)); /* now, LSW */
+ put_unaligned_uint32(rtcpheader + len + 8, htonl(rtcp_report->sender_information.rtp_timestamp));
+ put_unaligned_uint32(rtcpheader + len + 12, htonl(rtcp_report->sender_information.packet_count));
+ put_unaligned_uint32(rtcpheader + len + 16, htonl(rtcp_report->sender_information.octet_count));
len += 20;
}
if (report_block) {
- rtcpheader[2 + header_offset] = htonl(report_block->source_ssrc); /* Their SSRC */
- rtcpheader[3 + header_offset] = htonl((report_block->lost_count.fraction << 24) | report_block->lost_count.packets);
- rtcpheader[4 + header_offset] = htonl(report_block->highest_seq_no);
- rtcpheader[5 + header_offset] = htonl(report_block->ia_jitter);
- rtcpheader[6 + header_offset] = htonl(report_block->lsr);
- rtcpheader[7 + header_offset] = htonl(report_block->dlsr);
+ put_unaligned_uint32(rtcpheader + len, htonl(report_block->source_ssrc)); /* Their SSRC */
+ put_unaligned_uint32(rtcpheader + len + 4, htonl((report_block->lost_count.fraction << 24) | report_block->lost_count.packets));
+ put_unaligned_uint32(rtcpheader + len + 8, htonl(report_block->highest_seq_no));
+ put_unaligned_uint32(rtcpheader + len + 12, htonl(report_block->ia_jitter));
+ put_unaligned_uint32(rtcpheader + len + 16, htonl(report_block->lsr));
+ put_unaligned_uint32(rtcpheader + len + 20, htonl(report_block->dlsr));
len += 24;
}
- rtcpheader[0] = htonl((2 << 30) | (rtcp_report->reception_report_count << 24)
- | ((sr ? RTCP_PT_SR : RTCP_PT_RR) << 16) | ((len/4)-1));
- /* Insert SDES here. Probably should make SDES text equal to mimetypes[code].type (not subtype 'cos */
- /* it can change mid call, and SDES can't) */
- rtcpheader[len/4] = htonl((2 << 30) | (1 << 24) | (RTCP_PT_SDES << 16) | 2);
- rtcpheader[(len/4)+1] = htonl(rtcp_report->ssrc);
- rtcpheader[(len/4)+2] = htonl(0x01 << 24);
- len += 12;
+ put_unaligned_uint32(rtcpheader, htonl((2 << 30) | (rtcp_report->reception_report_count << 24)
+ | ((sr ? RTCP_PT_SR : RTCP_PT_RR) << 16) | ((len/4)-1)));
- ast_sockaddr_copy(&remote_address, &rtp->rtcp->them);
+ put_unaligned_uint32(rtcpheader + len, htonl((2 << 30) | (1 << 24) | (RTCP_PT_SDES << 16) | (2 + (AST_UUID_STR_LEN / 4))));
+ put_unaligned_uint32(rtcpheader + len + 4, htonl(rtcp_report->ssrc));
+ put_unaligned_uint16(rtcpheader + len + 8, htonl(0x01 << 24));
+ put_unaligned_uint16(rtcpheader + len + 9, htonl(AST_UUID_STR_LEN << 24));
+ memcpy(rtcpheader + len + 10, rtp->cname, AST_UUID_STR_LEN);
+ len += 12 + AST_UUID_STR_LEN;
+
+ if (rtp->bundled) {
+ ast_rtp_instance_get_remote_address(instance, &remote_address);
+ } else {
+ ast_sockaddr_copy(&remote_address, &rtp->rtcp->them);
+ }
res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, &remote_address, &ice);
if (res < 0) {
ast_log(LOG_ERROR, "RTCP %s transmission error to %s, rtcp halted %s\n",
@@ -3754,6 +3899,7 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr
unsigned int ms = calc_txstamp(rtp, &frame->delivery);
struct ast_sockaddr remote_address = { {0,} };
int rate = rtp_get_rate(frame->subclass.format) / 1000;
+ unsigned int seqno;
if (ast_format_cmp(frame->subclass.format, ast_format_g722) == AST_FORMAT_CMP_EQUAL) {
frame->samples /= 2;
@@ -3820,6 +3966,40 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr
rtp->lastdigitts = rtp->lastts;
}
+ /* Assume that the sequence number we expect to use is what will be used until proven otherwise */
+ seqno = rtp->seqno;
+
+ /* If the frame contains sequence number information use it to influence our sequence number */
+ if (ast_test_flag(frame, AST_FRFLAG_HAS_SEQUENCE_NUMBER)) {
+ if (rtp->expectedseqno != -1) {
+ /* Determine where the frame from the core is in relation to where we expected */
+ int difference = frame->seqno - rtp->expectedseqno;
+
+ /* If there is a substantial difference then we've either got packets really out
+ * of order, or the source is RTP and it has cycled. If this happens we resync
+ * the sequence number adjustments to this frame. If we also have packet loss
+ * things won't be reflected correctly but it will sort itself out after a bit.
+ */
+ if (abs(difference) > 100) {
+ difference = 0;
+ }
+
+ /* Adjust the sequence number being used for this packet accordingly */
+ seqno += difference;
+
+ if (difference >= 0) {
+ /* This frame is on time or in the future */
+ rtp->expectedseqno = frame->seqno + 1;
+ rtp->seqno += difference;
+ }
+ } else {
+ /* This is the first frame with sequence number we've seen, so start keeping track */
+ rtp->expectedseqno = frame->seqno + 1;
+ }
+ } else {
+ rtp->expectedseqno = -1;
+ }
+
if (ast_test_flag(frame, AST_FRFLAG_HAS_TIMING_INFO)) {
rtp->lastts = frame->ts * rate;
}
@@ -3831,7 +4011,7 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr
int hdrlen = 12, res, ice;
unsigned char *rtpheader = (unsigned char *)(frame->data.ptr - hdrlen);
- put_unaligned_uint32(rtpheader, htonl((2 << 30) | (codec << 16) | (rtp->seqno) | (mark << 23)));
+ put_unaligned_uint32(rtpheader, htonl((2 << 30) | (codec << 16) | (seqno) | (mark << 23)));
put_unaligned_uint32(rtpheader + 4, htonl(rtp->lastts));
put_unaligned_uint32(rtpheader + 8, htonl(rtp->ssrc));
@@ -3868,7 +4048,13 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr
}
}
- rtp->seqno++;
+ /* If the sequence number that has been used doesn't match what we expected then this is an out of
+ * order late packet, so we don't need to increment as we haven't yet gotten the expected frame from
+ * the core.
+ */
+ if (seqno == rtp->seqno) {
+ rtp->seqno++;
+ }
return 0;
}
@@ -3931,7 +4117,6 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr
/* VP8: is this a request to send a RTCP FIR? */
if (frame->frametype == AST_FRAME_CONTROL && frame->subclass.integer == AST_CONTROL_VIDUPDATE) {
- struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
unsigned int *rtcpheader;
char bdata[1024];
int len = 20;
@@ -3961,7 +4146,7 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr
rtcpheader[2] = htonl(rtp->themssrc);
rtcpheader[3] = htonl(rtp->themssrc); /* FCI: SSRC */
rtcpheader[4] = htonl(rtp->rtcp->firseq << 24); /* FCI: Sequence number */
- res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, &rtp->rtcp->them, &ice);
+ res = rtcp_sendto(instance, (unsigned int *)rtcpheader, len, 0, rtp->bundled ? &remote_address : &rtp->rtcp->them, &ice);
if (res < 0) {
ast_log(LOG_ERROR, "RTCP FIR transmission error: %s\n", strerror(errno));
}
@@ -4526,9 +4711,29 @@ static void update_lost_stats(struct ast_rtp *rtp, unsigned int lost_packets)
rtp->rtcp->reported_normdev_lost = reported_normdev_lost_current;
}
+/*! \pre instance is locked */
+static struct ast_rtp_instance *rtp_find_instance_by_ssrc(struct ast_rtp_instance *instance,
+ struct ast_rtp *rtp, unsigned int ssrc)
+{
+ int index;
+ struct ast_rtp_instance *found = instance;
+
+ for (index = 0; index < AST_VECTOR_SIZE(&rtp->ssrc_mapping); ++index) {
+ struct rtp_ssrc_mapping *mapping = AST_VECTOR_GET_ADDR(&rtp->ssrc_mapping, index);
+
+ if (mapping->ssrc == ssrc) {
+ found = mapping->instance;
+ break;
+ }
+ }
+
+ return found;
+}
+
static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, const unsigned char *rtcpdata, size_t size, struct ast_sockaddr *addr)
{
- struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+ struct ast_rtp_instance *transport = instance;
+ struct ast_rtp *transport_rtp = ast_rtp_instance_get_data(instance);
unsigned int *rtcpheader = (unsigned int *)(rtcpdata);
int packetwords, position = 0;
int report_counter = 0;
@@ -4537,13 +4742,13 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
packetwords = size / 4;
- if (ast_rtp_instance_get_prop(instance, AST_RTP_PROPERTY_NAT)) {
+ if (ast_rtp_instance_get_prop(transport, AST_RTP_PROPERTY_NAT)) {
/* Send to whoever sent to us */
- if (ast_sockaddr_cmp(&rtp->rtcp->them, addr)) {
- ast_sockaddr_copy(&rtp->rtcp->them, addr);
+ if (ast_sockaddr_cmp(&transport_rtp->rtcp->them, addr)) {
+ ast_sockaddr_copy(&transport_rtp->rtcp->them, addr);
if (rtpdebug) {
ast_debug(0, "RTCP NAT: Got RTCP from other end. Now sending to address %s\n",
- ast_sockaddr_stringify(&rtp->rtcp->them));
+ ast_sockaddr_stringify(&transport_rtp->rtcp->them));
}
}
}
@@ -4555,6 +4760,8 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
unsigned int length;
struct ast_json *message_blob;
RAII_VAR(struct ast_rtp_rtcp_report *, rtcp_report, NULL, ao2_cleanup);
+ struct ast_rtp_instance *child;
+ struct ast_rtp *rtp;
i = position;
length = ntohl(rtcpheader[i]);
@@ -4586,6 +4793,21 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
ast_verbose("SSRC of sender: %u\n", rtcp_report->ssrc);
}
+ /* Determine the appropriate instance for this */
+ child = rtp_find_instance_by_ssrc(transport, transport_rtp, rtcp_report->ssrc);
+ if (child != transport) {
+ /* It is safe to hold the child lock while holding the parent lock, we guarantee that the locking order
+ * is always parent->child or that the child lock is not held when acquiring the parent lock.
+ */
+ ao2_lock(child);
+ instance = child;
+ rtp = ast_rtp_instance_get_data(instance);
+ } else {
+ /* The child is the parent! We don't need to unlock it. */
+ child = NULL;
+ rtp = transport_rtp;
+ }
+
i += 2; /* Advance past header and ssrc */
switch (pt) {
case RTCP_PT_SR:
@@ -4621,6 +4843,9 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
/* Don't handle multiple reception reports (rc > 1) yet */
report_block = ast_calloc(1, sizeof(*report_block));
if (!report_block) {
+ if (child) {
+ ao2_unlock(child);
+ }
return &ast_null_frame;
}
rtcp_report->report_block[report_counter] = report_block;
@@ -4667,8 +4892,8 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
*/
message_blob = ast_json_pack("{s: s, s: s, s: f}",
- "from", ast_sockaddr_stringify(&rtp->rtcp->them),
- "to", rtp->rtcp->local_addr_str,
+ "from", ast_sockaddr_stringify(&transport_rtp->rtcp->them),
+ "to", transport_rtp->rtcp->local_addr_str,
"rtt", rtp->rtcp->rtt);
ast_rtp_publish_rtcp_message(instance, ast_rtp_rtcp_received_type(),
rtcp_report,
@@ -4677,26 +4902,26 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
/* Return an AST_FRAME_RTCP frame with the ast_rtp_rtcp_report
* object as a its data */
- rtp->f.frametype = AST_FRAME_RTCP;
- rtp->f.data.ptr = rtp->rtcp->frame_buf + AST_FRIENDLY_OFFSET;
- memcpy(rtp->f.data.ptr, rtcp_report, sizeof(struct ast_rtp_rtcp_report));
- rtp->f.datalen = sizeof(struct ast_rtp_rtcp_report);
+ transport_rtp->f.frametype = AST_FRAME_RTCP;
+ transport_rtp->f.data.ptr = rtp->rtcp->frame_buf + AST_FRIENDLY_OFFSET;
+ memcpy(transport_rtp->f.data.ptr, rtcp_report, sizeof(struct ast_rtp_rtcp_report));
+ transport_rtp->f.datalen = sizeof(struct ast_rtp_rtcp_report);
if (rc > 0) {
/* There's always a single report block stored, here */
struct ast_rtp_rtcp_report *rtcp_report2;
- report_block = rtp->f.data.ptr + rtp->f.datalen + sizeof(struct ast_rtp_rtcp_report_block *);
+ report_block = transport_rtp->f.data.ptr + transport_rtp->f.datalen + sizeof(struct ast_rtp_rtcp_report_block *);
memcpy(report_block, rtcp_report->report_block[report_counter-1], sizeof(struct ast_rtp_rtcp_report_block));
- rtcp_report2 = (struct ast_rtp_rtcp_report *)rtp->f.data.ptr;
+ rtcp_report2 = (struct ast_rtp_rtcp_report *)transport_rtp->f.data.ptr;
rtcp_report2->report_block[report_counter-1] = report_block;
- rtp->f.datalen += sizeof(struct ast_rtp_rtcp_report_block);
+ transport_rtp->f.datalen += sizeof(struct ast_rtp_rtcp_report_block);
}
- rtp->f.offset = AST_FRIENDLY_OFFSET;
- rtp->f.samples = 0;
- rtp->f.mallocd = 0;
- rtp->f.delivery.tv_sec = 0;
- rtp->f.delivery.tv_usec = 0;
- rtp->f.src = "RTP";
- f = &rtp->f;
+ transport_rtp->f.offset = AST_FRIENDLY_OFFSET;
+ transport_rtp->f.samples = 0;
+ transport_rtp->f.mallocd = 0;
+ transport_rtp->f.delivery.tv_sec = 0;
+ transport_rtp->f.delivery.tv_usec = 0;
+ transport_rtp->f.src = "RTP";
+ f = &transport_rtp->f;
break;
case RTCP_PT_FUR:
/* Handle RTCP FIR as FUR */
@@ -4704,34 +4929,38 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, c
if (rtcp_debug_test_addr(addr)) {
ast_verbose("Received an RTCP Fast Update Request\n");
}
- rtp->f.frametype = AST_FRAME_CONTROL;
- rtp->f.subclass.integer = AST_CONTROL_VIDUPDATE;
- rtp->f.datalen = 0;
- rtp->f.samples = 0;
- rtp->f.mallocd = 0;
- rtp->f.src = "RTP";
- f = &rtp->f;
+ transport_rtp->f.frametype = AST_FRAME_CONTROL;
+ transport_rtp->f.subclass.integer = AST_CONTROL_VIDUPDATE;
+ transport_rtp->f.datalen = 0;
+ transport_rtp->f.samples = 0;
+ transport_rtp->f.mallocd = 0;
+ transport_rtp->f.src = "RTP";
+ f = &transport_rtp->f;
break;
case RTCP_PT_SDES:
if (rtcp_debug_test_addr(addr)) {
ast_verbose("Received an SDES from %s\n",
- ast_sockaddr_stringify(&rtp->rtcp->them));
+ ast_sockaddr_stringify(&transport_rtp->rtcp->them));
}
break;
case RTCP_PT_BYE:
if (rtcp_debug_test_addr(addr)) {
ast_verbose("Received a BYE from %s\n",
- ast_sockaddr_stringify(&rtp->rtcp->them));
+ ast_sockaddr_stringify(&transport_rtp->rtcp->them));
}
break;
default:
ast_debug(1, "Unknown RTCP packet (pt=%d) received from %s\n",
- pt, ast_sockaddr_stringify(&rtp->rtcp->them));
+ pt, ast_sockaddr_stringify(&transport_rtp->rtcp->them));
break;
}
position += (length + 1);
+ rtp->rtcp->rtcp_info = 1;
+
+ if (child) {
+ ao2_unlock(child);
+ }
}
- rtp->rtcp->rtcp_info = 1;
return f;
@@ -4750,6 +4979,12 @@ static struct ast_frame *ast_rtcp_read(struct ast_rtp_instance *instance)
/* Read in RTCP data from the socket */
if ((res = rtcp_recvfrom(instance, read_area, read_area_size,
0, &addr)) < 0) {
+ if (res == RTP_DTLS_ESTABLISHED) {
+ rtp->f.frametype = AST_FRAME_CONTROL;
+ rtp->f.subclass.integer = AST_CONTROL_SRCCHANGE;
+ return &rtp->f;
+ }
+
ast_assert(errno != EBADF);
if (errno != EAGAIN) {
ast_log(LOG_WARNING, "RTCP Read error: %s. Hanging up.\n",
@@ -4917,11 +5152,19 @@ static int bridge_p2p_rtp_write(struct ast_rtp_instance *instance,
return 0;
}
+static void rtp_instance_unlock(struct ast_rtp_instance *instance)
+{
+ if (instance) {
+ ao2_unlock(instance);
+ }
+}
+
/*! \pre instance is locked */
static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtcp)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
struct ast_rtp_instance *instance1;
+ RAII_VAR(struct ast_rtp_instance *, child, NULL, rtp_instance_unlock);
struct ast_sockaddr addr;
int res, hdrlen = 12, version, payloadtype, padding, mark, ext, cc, prev_seqno;
unsigned char *read_area = rtp->rawdata + AST_FRIENDLY_OFFSET;
@@ -4939,14 +5182,15 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
return &ast_null_frame;
}
- /* If we are currently sending DTMF to the remote party send a continuation packet */
- if (rtp->sending_digit) {
- ast_rtp_dtmf_continuation(instance);
- }
-
/* Actually read in the data from the socket */
if ((res = rtp_recvfrom(instance, read_area, read_area_size, 0,
&addr)) < 0) {
+ if (res == RTP_DTLS_ESTABLISHED) {
+ rtp->f.frametype = AST_FRAME_CONTROL;
+ rtp->f.subclass.integer = AST_CONTROL_SRCCHANGE;
+ return &rtp->f;
+ }
+
ast_assert(errno != EBADF);
if (errno != EAGAIN) {
ast_log(LOG_WARNING, "RTP Read error: %s. Hanging up.\n",
@@ -5059,6 +5303,33 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
}
}
+ /* If the version is not what we expected by this point then just drop the packet */
+ if (version != 2) {
+ return &ast_null_frame;
+ }
+
+ /* We use the SSRC to determine what RTP instance this packet is actually for */
+ ssrc = ntohl(rtpheader[2]);
+
+ /* Determine the appropriate instance for this */
+ child = rtp_find_instance_by_ssrc(instance, rtp, ssrc);
+ if (child != instance) {
+ /* It is safe to hold the child lock while holding the parent lock, we guarantee that the locking order
+ * is always parent->child or that the child lock is not held when acquiring the parent lock.
+ */
+ ao2_lock(child);
+ instance = child;
+ rtp = ast_rtp_instance_get_data(instance);
+ } else {
+ /* The child is the parent! We don't need to unlock it. */
+ child = NULL;
+ }
+
+ /* If we are currently sending DTMF to the remote party send a continuation packet */
+ if (rtp->sending_digit) {
+ ast_rtp_dtmf_continuation(instance);
+ }
+
/* If we are directly bridged to another instance send the audio directly out */
instance1 = ast_rtp_instance_get_bridged(instance);
if (instance1
@@ -5066,11 +5337,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
return &ast_null_frame;
}
- /* If the version is not what we expected by this point then just drop the packet */
- if (version != 2) {
- return &ast_null_frame;
- }
-
/* Pull out the various other fields we will need */
payloadtype = (seqno & 0x7f0000) >> 16;
padding = seqno & (1 << 29);
@@ -5079,7 +5345,6 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
cc = (seqno & 0xF000000) >> 24;
seqno &= 0xffff;
timestamp = ntohl(rtpheader[1]);
- ssrc = ntohl(rtpheader[2]);
AST_LIST_HEAD_INIT_NOLOCK(&frames);
/* Force a marker bit and change SSRC if the SSRC changes */
@@ -5252,7 +5517,9 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
rtp->f.datalen = res - hdrlen;
rtp->f.data.ptr = read_area + hdrlen;
rtp->f.offset = hdrlen + AST_FRIENDLY_OFFSET;
+ ast_set_flag(&rtp->f, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
rtp->f.seqno = seqno;
+ rtp->f.stream_num = rtp->stream_num;
if ((ast_format_cmp(rtp->f.subclass.format, ast_format_t140) == AST_FORMAT_CMP_EQUAL)
&& ((int)seqno - (prev_seqno + 1) > 0)
@@ -5514,6 +5781,7 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
struct ast_sockaddr local;
+ int index;
ast_rtp_instance_get_local_address(instance, &local);
if (!ast_sockaddr_isnull(addr)) {
@@ -5542,6 +5810,13 @@ static void ast_rtp_remote_address_set(struct ast_rtp_instance *instance, struct
rtp->rtcp->local_addr_str = ast_strdup(ast_sockaddr_stringify(&local));
}
+ /* Update any bundled RTP instances */
+ for (index = 0; index < AST_VECTOR_SIZE(&rtp->ssrc_mapping); ++index) {
+ struct rtp_ssrc_mapping *mapping = AST_VECTOR_GET_ADDR(&rtp->ssrc_mapping, index);
+
+ ast_rtp_instance_set_remote_address(mapping->instance, addr);
+ }
+
rtp->rxseqno = 0;
if (strictrtp && rtp->strict_rtp_state != STRICT_RTP_OPEN) {
@@ -5825,43 +6100,107 @@ static unsigned int ast_rtp_get_ssrc(struct ast_rtp_instance *instance)
/*! \pre instance is locked */
static const char *ast_rtp_get_cname(struct ast_rtp_instance *instance)
{
- /* XXX
- *
- * Asterisk currently puts a zero-length CNAME value in RTCP SDES items,
- * meaning our CNAME will always be an empty string. In future, should
- * Asterisk actually start using meaningful CNAMEs, this function will
- * need to return that instead of an empty string
- */
- return "";
+ struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+
+ return rtp->cname;
}
-#ifdef HAVE_OPENSSL_SRTP
-static void dtls_perform_setup(struct dtls_details *dtls)
+static void ast_rtp_set_remote_ssrc(struct ast_rtp_instance *instance, unsigned int ssrc)
{
- if (!dtls->ssl || !SSL_is_init_finished(dtls->ssl)) {
+ struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+
+ if (rtp->themssrc) {
return;
}
- SSL_clear(dtls->ssl);
- if (dtls->dtls_setup == AST_RTP_DTLS_SETUP_PASSIVE) {
- SSL_set_accept_state(dtls->ssl);
- } else {
- SSL_set_connect_state(dtls->ssl);
- }
- dtls->connection = AST_RTP_DTLS_CONNECTION_NEW;
+ rtp->themssrc = ssrc;
}
-/*! \pre instance is locked */
-static int ast_rtp_activate(struct ast_rtp_instance *instance)
+static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num)
{
struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
- dtls_perform_setup(&rtp->dtls);
+ rtp->stream_num = stream_num;
+}
- if (rtp->rtcp) {
- dtls_perform_setup(&rtp->rtcp->dtls);
+static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent)
+{
+ struct ast_rtp *child_rtp = ast_rtp_instance_get_data(child);
+ struct ast_rtp *parent_rtp;
+ struct rtp_ssrc_mapping mapping;
+ struct ast_sockaddr them = { { 0, } };
+
+ if (child_rtp->bundled == parent) {
+ return 0;
+ }
+
+ /* If this instance was already bundled then remove the SSRC mapping */
+ if (child_rtp->bundled) {
+ struct ast_rtp *bundled_rtp;
+
+ ao2_unlock(child);
+
+ /* The child lock can't be held while accessing the parent */
+ ao2_lock(child_rtp->bundled);
+ bundled_rtp = ast_rtp_instance_get_data(child_rtp->bundled);
+ AST_VECTOR_REMOVE_CMP_UNORDERED(&bundled_rtp->ssrc_mapping, child, SSRC_MAPPING_ELEM_CMP, AST_VECTOR_ELEM_CLEANUP_NOOP);
+ ao2_unlock(child_rtp->bundled);
+
+ ao2_lock(child);
+ ao2_ref(child_rtp->bundled, -1);
+ child_rtp->bundled = NULL;
+ }
+
+ if (!parent) {
+ /* We transitioned away from bundle so we need our own transport resources once again */
+ rtp_allocate_transport(child, child_rtp);
+ return 0;
}
+ parent_rtp = ast_rtp_instance_get_data(parent);
+
+ /* We no longer need any transport related resources as we will use our parent RTP instance instead */
+ rtp_deallocate_transport(child, child_rtp);
+
+ /* Children maintain a reference to the parent to guarantee that the transport doesn't go away on them */
+ child_rtp->bundled = ao2_bump(parent);
+
+ mapping.ssrc = child_rtp->themssrc;
+ mapping.instance = child;
+
+ ao2_unlock(child);
+
+ ao2_lock(parent);
+
+ AST_VECTOR_APPEND(&parent_rtp->ssrc_mapping, mapping);
+
+#ifdef HAVE_OPENSSL_SRTP
+ /* If DTLS-SRTP is already in use then add the local SSRC to it, otherwise it will get added once DTLS
+ * negotiation has been completed.
+ */
+ if (parent_rtp->dtls.connection == AST_RTP_DTLS_CONNECTION_EXISTING) {
+ dtls_srtp_add_local_ssrc(parent_rtp, ast_rtp_instance_get_srtp(parent, 0), parent, 0, child_rtp->ssrc, 0);
+ }
+#endif
+
+ /* Bundle requires that RTCP-MUX be in use so only the main remote address needs to match */
+ ast_rtp_instance_get_remote_address(parent, &them);
+
+ ao2_unlock(parent);
+
+ ao2_lock(child);
+
+ ast_rtp_instance_set_remote_address(child, &them);
+
+ return 0;
+}
+
+#ifdef HAVE_OPENSSL_SRTP
+/*! \pre instance is locked */
+static int ast_rtp_activate(struct ast_rtp_instance *instance)
+{
+ struct ast_rtp *rtp = ast_rtp_instance_get_data(instance);
+
/* If ICE negotiation is enabled the DTLS Handshake will be performed upon completion of it */
#ifdef HAVE_PJPROJECT
if (rtp->ice) {
@@ -5869,9 +6208,11 @@ static int ast_rtp_activate(struct ast_rtp_instance *instance)
}
#endif
+ dtls_perform_setup(&rtp->dtls);
dtls_perform_handshake(instance, &rtp->dtls, 0);
if (rtp->rtcp && rtp->rtcp->type == AST_RTP_INSTANCE_RTCP_STANDARD) {
+ dtls_perform_setup(&rtp->rtcp->dtls);
dtls_perform_handshake(instance, &rtp->rtcp->dtls, 1);
}
diff --git a/res/res_sdp_translator_pjmedia.c b/res/res_sdp_translator_pjmedia.c
index 85f246e83..d80f3d554 100644
--- a/res/res_sdp_translator_pjmedia.c
+++ b/res/res_sdp_translator_pjmedia.c
@@ -484,7 +484,7 @@ AST_TEST_DEFINE(pjmedia_to_sdp_test)
}
cleanup:
- ast_sdp_free(sdp);
+ ao2_cleanup(sdp);
ast_sdp_translator_free(translator);
pj_pool_release(pool);
return res;
@@ -560,7 +560,7 @@ AST_TEST_DEFINE(sdp_to_pjmedia_test)
}
cleanup:
- ast_sdp_free(sdp);
+ ao2_cleanup(sdp);
ast_sdp_translator_free(translator);
pj_pool_release(pool);
return res;
diff --git a/res/res_stasis.c b/res/res_stasis.c
index 9ea0d63fe..899c8f720 100644
--- a/res/res_stasis.c
+++ b/res/res_stasis.c
@@ -1069,8 +1069,18 @@ static void channel_stolen_cb(void *data, struct ast_channel *old_chan, struct a
{
struct stasis_app_control *control;
- /* find control */
- control = ao2_callback(app_controls, 0, masq_match_cb, old_chan);
+ /*
+ * At this point, old_chan is the channel pointer that is in Stasis() and
+ * has the unknown channel's name in it while new_chan is the channel pointer
+ * that is not in Stasis(), but has the guts of the channel that Stasis() knows
+ * about.
+ *
+ * Find and unlink control since the channel has a new name/uniqueid
+ * and its hash has changed. Since the channel is leaving stasis don't
+ * bother putting it back into the container. Nobody is going to
+ * remove it from the container later.
+ */
+ control = ao2_callback(app_controls, OBJ_UNLINK, masq_match_cb, old_chan);
if (!control) {
ast_log(LOG_ERROR, "Could not find control for masqueraded channel\n");
return;
@@ -1111,8 +1121,10 @@ static void channel_replaced_cb(void *data, struct ast_channel *old_chan, struct
return;
}
- /* find, unlink, and relink control since the channel has a new name and
- * its hash has likely changed */
+ /*
+ * Find, unlink, and relink control since the channel has a new
+ * name/uniqueid and its hash has changed.
+ */
control = ao2_callback(app_controls, OBJ_UNLINK, masq_match_cb, new_chan);
if (!control) {
ast_log(LOG_ERROR, "Could not find control for masquerading channel\n");
diff --git a/res/res_stasis_device_state.c b/res/res_stasis_device_state.c
index 344cb40c9..276a98b93 100644
--- a/res/res_stasis_device_state.c
+++ b/res/res_stasis_device_state.c
@@ -106,7 +106,6 @@ static int device_state_subscriptions_cmp(void *obj, void *arg, int flags)
static void device_state_subscription_destroy(void *obj)
{
struct device_state_subscription *sub = obj;
- sub->sub = stasis_unsubscribe_and_join(sub->sub);
ast_string_field_free_memory(sub);
}
@@ -152,6 +151,9 @@ static struct device_state_subscription *find_device_state_subscription(
static void remove_device_state_subscription(
struct device_state_subscription *sub)
{
+ if (sub->sub) {
+ sub->sub = stasis_unsubscribe_and_join(sub->sub);
+ }
ao2_unlink_flags(device_state_subscriptions, sub, OBJ_NOLOCK);
}
diff --git a/res/res_stasis_snoop.c b/res/res_stasis_snoop.c
index cd51638ea..f797a9b94 100644
--- a/res/res_stasis_snoop.c
+++ b/res/res_stasis_snoop.c
@@ -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 */
diff --git a/rest-api-templates/make_ari_stubs.py b/rest-api-templates/make_ari_stubs.py
index 4e02cdd5f..0aba06d6d 100755
--- a/rest-api-templates/make_ari_stubs.py
+++ b/rest-api-templates/make_ari_stubs.py
@@ -41,7 +41,7 @@ def rel(file):
"""
return os.path.join(TOPDIR, file)
-WIKI_PREFIX = 'Asterisk 13'
+WIKI_PREFIX = 'Asterisk 16'
API_TRANSFORMS = [
Transform(rel('api.wiki.mustache'),
diff --git a/rest-api/resources.json b/rest-api/resources.json
index df5b8d6fd..920bb896d 100644
--- a/rest-api/resources.json
+++ b/rest-api/resources.json
@@ -2,7 +2,7 @@
"_copyright": "Copyright (C) 2012 - 2013, Digium, Inc.",
"_author": "David M. Lee, II <dlee@digium.com>",
"_svn_revision": "$Revision$",
- "apiVersion": "2.0.0",
+ "apiVersion": "3.0.0",
"swaggerVersion": "1.1",
"basePath": "http://localhost:8088/ari",
"apis": [
diff --git a/sounds/Makefile b/sounds/Makefile
index 84d0f45c0..381776f72 100644
--- a/sounds/Makefile
+++ b/sounds/Makefile
@@ -19,13 +19,14 @@ CMD_PREFIX?=@
SOUNDS_DIR:=$(DESTDIR)$(ASTDATADIR)/sounds
SOUNDS_CACHE_DIR?=
MOH_DIR:=$(DESTDIR)$(ASTDATADIR)/moh
-CORE_SOUNDS_VERSION:=1.5
-EXTRA_SOUNDS_VERSION:=1.5
+CORE_SOUNDS_VERSION:=1.6
+EXTRA_SOUNDS_VERSION:=1.5.1
MOH_VERSION:=2.03
SOUNDS_URL:=http://downloads.asterisk.org/pub/telephony/sounds/releases
MCS:=$(subst -EN-,-en-,$(MENUSELECT_CORE_SOUNDS))
MCS:=$(subst -EN_AU-,-en_AU-,$(MCS))
MCS:=$(subst -EN_GB-,-en_GB-,$(MCS))
+MCS:=$(subst -EN_NZ-,-en_NZ-,$(MCS))
MCS:=$(subst -FR-,-fr-,$(MCS))
MCS:=$(subst -ES-,-es-,$(MCS))
MCS:=$(subst -RU-,-ru-,$(MCS))
@@ -144,6 +145,8 @@ $(eval $(call sound_format_lang_rule,$(SOUNDS_DIR),core-sounds,en_AU,$(CORE_SOUN
$(eval $(call sound_format_lang_rule,$(SOUNDS_DIR),core-sounds,en_GB,$(CORE_SOUNDS_VERSION)))
+$(eval $(call sound_format_lang_rule,$(SOUNDS_DIR),core-sounds,en_NZ,$(CORE_SOUNDS_VERSION)))
+
$(eval $(call sound_format_lang_rule,$(SOUNDS_DIR),core-sounds,es,$(CORE_SOUNDS_VERSION)))
$(eval $(call sound_format_lang_rule,$(SOUNDS_DIR),core-sounds,fr,$(CORE_SOUNDS_VERSION)))
diff --git a/sounds/sounds.xml b/sounds/sounds.xml
index 547be4b4a..2d996c57e 100644
--- a/sounds/sounds.xml
+++ b/sounds/sounds.xml
@@ -81,6 +81,33 @@
<member name="CORE-SOUNDS-EN_GB-SIREN14" displayname="English (British Accent), G.722.1C (Siren14) format">
<support_level>core</support_level>
</member>
+ <member name="CORE-SOUNDS-EN_NZ-WAV" displayname="English (New Zealand Accent), WAV format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-ULAW" displayname="English (New Zealand Accent), mu-Law format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-ALAW" displayname="English (New Zealand Accent), a-Law format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-GSM" displayname="English (New Zealand Accent), GSM format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-G729" displayname="English (New Zealand Accent), G.729 format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-G722" displayname="English (New Zealand Accent), G.722 format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-SLN16" displayname="English (New Zealand Accent), Signed-linear 16kHz format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-SIREN7" displayname="English (New Zealand Accent), G.722.1 (Siren7) format">
+ <support_level>core</support_level>
+ </member>
+ <member name="CORE-SOUNDS-EN_NZ-SIREN14" displayname="English (New Zealand Accent), G.722.1C (Siren14) format">
+ <support_level>core</support_level>
+ </member>
<member name="CORE-SOUNDS-ES-WAV" displayname="Spanish, WAV format">
<support_level>core</support_level>
</member>
diff --git a/tests/test_config.c b/tests/test_config.c
index 6635c6f78..d73710860 100644
--- a/tests/test_config.c
+++ b/tests/test_config.c
@@ -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");
diff --git a/tests/test_sdp.c b/tests/test_sdp.c
index 7eef3f741..0ab8ec8ae 100644
--- a/tests/test_sdp.c
+++ b/tests/test_sdp.c
@@ -88,13 +88,38 @@ static int validate_m_line(struct ast_test *test, const struct ast_sdp_m_line *m
return -1;
}
+ if (m_line->port == 0) {
+ ast_test_status_update(test, "Expected %s m-line to not be declined\n",
+ media_type);
+ return -1;
+ }
+
if (ast_sdp_m_get_payload_count(m_line) != num_payloads) {
- ast_test_status_update(test, "Expected m-line payload count %d but got %d\n",
- num_payloads, ast_sdp_m_get_payload_count(m_line));
+ ast_test_status_update(test, "Expected %s m-line payload count %d but got %d\n",
+ media_type, num_payloads, ast_sdp_m_get_payload_count(m_line));
+ return -1;
+ }
+
+ ast_test_status_update(test, "SDP %s m-line is as expected\n", media_type);
+ return 0;
+}
+
+static int validate_m_line_declined(struct ast_test *test,
+ const struct ast_sdp_m_line *m_line, const char *media_type)
+{
+ if (strcmp(m_line->type, media_type)) {
+ ast_test_status_update(test, "Expected m-line media type %s but got %s\n",
+ media_type, m_line->type);
+ return -1;
+ }
+
+ if (m_line->port != 0) {
+ ast_test_status_update(test, "Expected %s m-line to be declined but got port %u\n",
+ media_type, m_line->port);
return -1;
}
- ast_test_status_update(test, "SDP m-line is as expected\n");
+ ast_test_status_update(test, "SDP %s m-line is as expected\n", media_type);
return 0;
}
@@ -438,6 +463,29 @@ struct sdp_format {
const char *formats;
};
+static int build_sdp_option_formats(struct ast_sdp_options *options, int num_streams, const struct sdp_format *formats)
+{
+ int idx;
+
+ for (idx = 0; idx < num_streams; ++idx) {
+ struct ast_format_cap *caps;
+
+ if (ast_strlen_zero(formats[idx].formats)) {
+ continue;
+ }
+
+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!caps
+ || ast_format_cap_update_by_allow_disallow(caps, formats[idx].formats, 1) < 0) {
+ ao2_cleanup(caps);
+ return -1;
+ }
+ ast_sdp_options_set_format_cap_type(options, formats[idx].type, caps);
+ ao2_cleanup(caps);
+ }
+ return 0;
+}
+
/*!
* \brief Common method to build an SDP state for a test.
*
@@ -450,9 +498,18 @@ struct sdp_format {
*
* \param num_streams The number of elements in the formats array.
* \param formats Array of media types and formats that will be in the state.
+ * \param opt_num_streams The number of new stream types allowed to create.
+ * Not used if test_options provided.
+ * \param opt_formats Array of new stream media types and formats allowed to create.
+ * NULL if use a default stream creation.
+ * Not used if test_options provided.
+ * \param max_streams 0 if set max to max(3, num_streams) else max(max_streams, num_streams)
+ * Not used if test_options provided.
* \param test_options Optional SDP options.
*/
-static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats, struct ast_sdp_options *test_options)
+static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats,
+ int opt_num_streams, const struct sdp_format *opt_formats, unsigned int max_streams,
+ struct ast_sdp_options *test_options)
{
struct ast_stream_topology *topology = NULL;
struct ast_sdp_state *state = NULL;
@@ -460,10 +517,34 @@ static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_f
int i;
if (!test_options) {
+ static const struct sdp_format sdp_formats[] = {
+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },
+ { AST_MEDIA_TYPE_VIDEO, "vp8" },
+ { AST_MEDIA_TYPE_IMAGE, "t38" },
+ };
+
options = sdp_options_common();
if (!options) {
goto end;
}
+
+ /* Determine max_streams to allow */
+ if (!max_streams) {
+ max_streams = ARRAY_LEN(sdp_formats);
+ }
+ if (max_streams < num_streams) {
+ max_streams = num_streams;
+ }
+ ast_sdp_options_set_max_streams(options, max_streams);
+
+ /* Determine new stream formats and types allowed */
+ if (!opt_formats) {
+ opt_num_streams = ARRAY_LEN(sdp_formats);
+ opt_formats = sdp_formats;
+ }
+ if (build_sdp_option_formats(options, opt_num_streams, opt_formats)) {
+ goto end;
+ }
} else {
options = test_options;
}
@@ -474,22 +555,31 @@ static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_f
}
for (i = 0; i < num_streams; ++i) {
- RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);
struct ast_stream *stream;
- caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
- if (!caps) {
+ stream = ast_stream_alloc("sure_thing", formats[i].type);
+ if (!stream) {
goto end;
}
- if (ast_format_cap_update_by_allow_disallow(caps, formats[i].formats, 1) < 0) {
- goto end;
+ if (!ast_strlen_zero(formats[i].formats)) {
+ struct ast_format_cap *caps;
+
+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!caps
+ || ast_format_cap_update_by_allow_disallow(caps, formats[i].formats, 1) < 0) {
+ ao2_cleanup(caps);
+ ast_stream_free(stream);
+ goto end;
+ }
+ ast_stream_set_formats(stream, caps);
+ ao2_cleanup(caps);
+ } else {
+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
}
- stream = ast_stream_alloc("sure_thing", formats[i].type);
- if (!stream) {
+ if (ast_stream_topology_append_stream(topology, stream) < 0) {
+ ast_stream_free(stream);
goto end;
}
- ast_stream_set_formats(stream, caps);
- ast_stream_topology_append_stream(topology, stream);
}
state = ast_sdp_state_alloc(topology, options);
@@ -530,7 +620,8 @@ AST_TEST_DEFINE(topology_to_sdp)
break;
}
- sdp_state = build_sdp_state(ARRAY_LEN(formats), formats, NULL);
+ sdp_state = build_sdp_state(ARRAY_LEN(formats), formats,
+ ARRAY_LEN(formats), formats, 0, NULL);
if (!sdp_state) {
goto end;
}
@@ -674,7 +765,8 @@ AST_TEST_DEFINE(sdp_to_topology)
break;
}
- sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats, NULL);
+ sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats,
+ ARRAY_LEN(sdp_formats), sdp_formats, 0, NULL);
if (!sdp_state) {
res = AST_TEST_FAIL;
goto end;
@@ -723,7 +815,7 @@ end:
return res;
}
-static int validate_merged_sdp(struct ast_test *test, const struct ast_sdp *sdp)
+static int validate_avi_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp)
{
struct ast_sdp_m_line *m_line;
@@ -769,7 +861,12 @@ static int validate_merged_sdp(struct ast_test *test, const struct ast_sdp *sdp)
return 0;
}
-AST_TEST_DEFINE(sdp_merge_symmetric)
+static enum ast_test_result_state sdp_negotiation_completed_tests(struct ast_test *test,
+ int offer_num_streams, const struct sdp_format *offer_formats,
+ int answer_num_streams, const struct sdp_format *answer_formats,
+ int allowed_ans_num_streams, const struct sdp_format *allowed_ans_formats,
+ unsigned int max_streams,
+ int (*validate_sdp)(struct ast_test *test, const struct ast_sdp *sdp))
{
enum ast_test_result_state res = AST_TEST_PASS;
struct ast_sdp_state *sdp_state_offerer = NULL;
@@ -777,65 +874,68 @@ AST_TEST_DEFINE(sdp_merge_symmetric)
const struct ast_sdp *offerer_sdp;
const struct ast_sdp *answerer_sdp;
- static const struct sdp_format offerer_formats[] = {
- { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },
- { AST_MEDIA_TYPE_VIDEO, "h264,vp8" },
- { AST_MEDIA_TYPE_IMAGE, "t38" },
- };
- static const struct sdp_format answerer_formats[] = {
- { AST_MEDIA_TYPE_AUDIO, "ulaw" },
- { AST_MEDIA_TYPE_VIDEO, "vp8" },
- { AST_MEDIA_TYPE_IMAGE, "t38" },
- };
-
- switch(cmd) {
- case TEST_INIT:
- info->name = "sdp_merge_symmetric";
- info->category = "/main/sdp/";
- info->summary = "Merge two SDPs with symmetric stream types";
- info->description =
- "SDPs 1 and 2 each have one audio and one video stream (in that order).\n"
- "SDP 1 offers to SDP 2, who answers. We ensure that both local SDPs have\n"
- "the expected stream types and the expected formats";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
-
- sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL);
+ sdp_state_offerer = build_sdp_state(offer_num_streams, offer_formats,
+ offer_num_streams, offer_formats, max_streams, NULL);
if (!sdp_state_offerer) {
+ ast_test_status_update(test, "Building offerer SDP state failed\n");
res = AST_TEST_FAIL;
goto end;
}
- sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL);
+ sdp_state_answerer = build_sdp_state(answer_num_streams, answer_formats,
+ allowed_ans_num_streams, allowed_ans_formats, max_streams, NULL);
if (!sdp_state_answerer) {
+ ast_test_status_update(test, "Building answerer SDP state failed\n");
res = AST_TEST_FAIL;
goto end;
}
offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);
if (!offerer_sdp) {
+ ast_test_status_update(test, "Building offerer offer failed\n");
res = AST_TEST_FAIL;
goto end;
}
- ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp);
+ if (ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp)) {
+ ast_test_status_update(test, "Setting answerer offer failed\n");
+ res = AST_TEST_FAIL;
+ goto end;
+ }
answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer);
if (!answerer_sdp) {
+ ast_test_status_update(test, "Building answerer answer failed\n");
res = AST_TEST_FAIL;
goto end;
}
- ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp);
+ if (ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp)) {
+ ast_test_status_update(test, "Setting offerer answer failed\n");
+ res = AST_TEST_FAIL;
+ goto end;
+ }
- /* Get the offerer SDP again because it's now going to be the joint SDP */
+ /*
+ * Restart SDP negotiations to build the joint SDP on the offerer
+ * side. Otherwise we will get the original offer for use in
+ * case of retransmissions.
+ */
+ if (ast_sdp_state_restart_negotiations(sdp_state_offerer)) {
+ ast_test_status_update(test, "Restarting negotiations failed\n");
+ res = AST_TEST_FAIL;
+ goto end;
+ }
offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);
- if (validate_merged_sdp(test, offerer_sdp)) {
+ if (!offerer_sdp) {
+ ast_test_status_update(test, "Building offerer current sdp failed\n");
+ res = AST_TEST_FAIL;
+ goto end;
+ }
+ if (validate_sdp(test, offerer_sdp)) {
res = AST_TEST_FAIL;
goto end;
}
- if (validate_merged_sdp(test, answerer_sdp)) {
+ if (validate_sdp(test, answerer_sdp)) {
res = AST_TEST_FAIL;
goto end;
}
@@ -847,14 +947,38 @@ end:
return res;
}
-AST_TEST_DEFINE(sdp_merge_crisscross)
+AST_TEST_DEFINE(sdp_negotiation_initial)
{
- enum ast_test_result_state res = AST_TEST_PASS;
- struct ast_sdp_state *sdp_state_offerer = NULL;
- struct ast_sdp_state *sdp_state_answerer = NULL;
- const struct ast_sdp *offerer_sdp;
- const struct ast_sdp *answerer_sdp;
+ static const struct sdp_format offerer_formats[] = {
+ { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },
+ { AST_MEDIA_TYPE_VIDEO, "h264,vp8" },
+ { AST_MEDIA_TYPE_IMAGE, "t38" },
+ };
+ switch(cmd) {
+ case TEST_INIT:
+ info->name = "sdp_negotiation_initial";
+ info->category = "/main/sdp/";
+ info->summary = "Simulate an initial negotiation";
+ info->description =
+ "Initial negotiation tests creating new streams on the answering side.\n"
+ "After negotiation both offerer and answerer sides should have the same\n"
+ "expected stream types and formats.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return sdp_negotiation_completed_tests(test,
+ ARRAY_LEN(offerer_formats), offerer_formats,
+ 0, NULL,
+ 0, NULL,
+ 0,
+ validate_avi_sdp_streams);
+}
+
+AST_TEST_DEFINE(sdp_negotiation_type_change)
+{
static const struct sdp_format offerer_formats[] = {
{ AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },
{ AST_MEDIA_TYPE_VIDEO, "h264,vp8" },
@@ -868,82 +992,66 @@ AST_TEST_DEFINE(sdp_merge_crisscross)
switch(cmd) {
case TEST_INIT:
- info->name = "sdp_merge_crisscross";
+ info->name = "sdp_negotiation_type_change";
info->category = "/main/sdp/";
- info->summary = "Merge two SDPs with symmetric stream types";
+ info->summary = "Simulate a re-negotiation changing stream types";
info->description =
- "SDPs 1 and 2 each have one audio and one video stream. However, SDP 1 and\n"
- "2 natively have the formats in a different order.\n"
- "SDP 1 offers to SDP 2, who answers. We ensure that both local SDPs have\n"
- "the expected stream types and the expected formats. Since SDP 1 was the\n"
- "offerer, the format order on SDP 1 should determine the order of formats in the SDPs";
+ "Reinvite negotiation tests changing stream types on the answering side.\n"
+ "After negotiation both offerer and answerer sides should have the same\n"
+ "expected stream types and formats.";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
- sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL);
- if (!sdp_state_offerer) {
- res = AST_TEST_FAIL;
- goto end;
- }
+ return sdp_negotiation_completed_tests(test,
+ ARRAY_LEN(offerer_formats), offerer_formats,
+ ARRAY_LEN(answerer_formats), answerer_formats,
+ 0, NULL,
+ 0,
+ validate_avi_sdp_streams);
+}
- sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL);
- if (!sdp_state_answerer) {
- res = AST_TEST_FAIL;
- goto end;
- }
+static int validate_aviavia_declined_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp)
+{
+ struct ast_sdp_m_line *m_line;
- offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);
- if (!offerer_sdp) {
- res = AST_TEST_FAIL;
- goto end;
+ if (!sdp) {
+ return -1;
}
- ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp);
- answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer);
- if (!answerer_sdp) {
- res = AST_TEST_FAIL;
- goto end;
+ m_line = ast_sdp_get_m(sdp, 0);
+ if (validate_m_line_declined(test, m_line, "audio")) {
+ return -1;
}
- ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp);
-
- /* Get the offerer SDP again because it's now going to be the joint SDP */
- offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);
- if (validate_merged_sdp(test, offerer_sdp)) {
- res = AST_TEST_FAIL;
- goto end;
- }
- if (validate_merged_sdp(test, answerer_sdp)) {
- res = AST_TEST_FAIL;
- goto end;
+ m_line = ast_sdp_get_m(sdp, 1);
+ if (validate_m_line_declined(test, m_line, "video")) {
+ return -1;
}
-end:
- ast_sdp_state_free(sdp_state_offerer);
- ast_sdp_state_free(sdp_state_answerer);
-
- return res;
-}
+ m_line = ast_sdp_get_m(sdp, 2);
+ if (validate_m_line_declined(test, m_line, "image")) {
+ return -1;
+ }
-static int validate_merged_sdp_asymmetric(struct ast_test *test, const struct ast_sdp *sdp, int is_offer)
-{
- struct ast_sdp_m_line *m_line;
- const char *side = is_offer ? "Offer side" : "Answer side";
+ m_line = ast_sdp_get_m(sdp, 3);
+ if (validate_m_line_declined(test, m_line, "audio")) {
+ return -1;
+ }
- if (!sdp) {
- ast_test_status_update(test, "%s does not have a SDP\n", side);
+ m_line = ast_sdp_get_m(sdp, 4);
+ if (validate_m_line_declined(test, m_line, "video")) {
return -1;
}
- /* Stream 0 */
- m_line = ast_sdp_get_m(sdp, 0);
- if (validate_m_line(test, m_line, "audio", 1)) {
+ m_line = ast_sdp_get_m(sdp, 5);
+ if (validate_m_line_declined(test, m_line, "image")) {
return -1;
}
- if (!m_line->port) {
- ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 0, "n't");
+
+ m_line = ast_sdp_get_m(sdp, 6);
+ if (validate_m_line(test, m_line, "audio", 1)) {
return -1;
}
if (validate_rtpmap(test, m_line, "PCMU")) {
@@ -954,88 +1062,154 @@ static int validate_merged_sdp_asymmetric(struct ast_test *test, const struct as
if (!validate_rtpmap(test, m_line, "PCMA")) {
return -1;
}
- if (!validate_rtpmap(test, m_line, "G722")) {
- return -1;
+
+ return 0;
+}
+
+AST_TEST_DEFINE(sdp_negotiation_decline_incompatible)
+{
+ static const struct sdp_format offerer_formats[] = {
+ /* Incompatible declined streams */
+ { AST_MEDIA_TYPE_AUDIO, "alaw" },
+ { AST_MEDIA_TYPE_VIDEO, "vp8" },
+ { AST_MEDIA_TYPE_IMAGE, "t38" },
+ /* Initially declined streams */
+ { AST_MEDIA_TYPE_AUDIO, "" },
+ { AST_MEDIA_TYPE_VIDEO, "" },
+ { AST_MEDIA_TYPE_IMAGE, "" },
+ /* Compatible stream so not all are declined */
+ { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw" },
+ };
+ static const struct sdp_format allowed_formats[] = {
+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },
+ };
+
+ switch(cmd) {
+ case TEST_INIT:
+ info->name = "sdp_negotiation_decline_incompatible";
+ info->category = "/main/sdp/";
+ info->summary = "Simulate an initial negotiation declining streams";
+ info->description =
+ "Initial negotiation tests declining incompatible streams.\n"
+ "After negotiation both offerer and answerer sides should have\n"
+ "the same expected stream types and formats.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
}
- if (!validate_rtpmap(test, m_line, "opus")) {
+
+ return sdp_negotiation_completed_tests(test,
+ ARRAY_LEN(offerer_formats), offerer_formats,
+ 0, NULL,
+ ARRAY_LEN(allowed_formats), allowed_formats,
+ ARRAY_LEN(offerer_formats),
+ validate_aviavia_declined_sdp_streams);
+}
+
+static int validate_aaaa_declined_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp)
+{
+ struct ast_sdp_m_line *m_line;
+
+ if (!sdp) {
return -1;
}
- /* The remaining streams should be declined */
+ m_line = ast_sdp_get_m(sdp, 0);
+ if (validate_m_line(test, m_line, "audio", 1)) {
+ return -1;
+ }
+ if (validate_rtpmap(test, m_line, "PCMU")) {
+ return -1;
+ }
- /* Stream 1 */
m_line = ast_sdp_get_m(sdp, 1);
if (validate_m_line(test, m_line, "audio", 1)) {
return -1;
}
- if (m_line->port) {
- ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 1, "");
+ if (validate_rtpmap(test, m_line, "PCMU")) {
return -1;
}
- /* Stream 2 */
m_line = ast_sdp_get_m(sdp, 2);
- if (validate_m_line(test, m_line, "video", 1)) {
+ if (validate_m_line(test, m_line, "audio", 1)) {
return -1;
}
- if (m_line->port) {
- ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 2, "");
+ if (validate_rtpmap(test, m_line, "PCMU")) {
return -1;
}
- /* Stream 3 */
m_line = ast_sdp_get_m(sdp, 3);
- if (validate_m_line(test, m_line, "image", 1)) {
- return -1;
- }
- if (m_line->port) {
- ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 3, "");
+ if (validate_m_line_declined(test, m_line, "audio")) {
return -1;
}
return 0;
}
-AST_TEST_DEFINE(sdp_merge_asymmetric)
+AST_TEST_DEFINE(sdp_negotiation_decline_max_streams)
+{
+ static const struct sdp_format offerer_formats[] = {
+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },
+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },
+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },
+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },
+ };
+
+ switch(cmd) {
+ case TEST_INIT:
+ info->name = "sdp_negotiation_decline_max_streams";
+ info->category = "/main/sdp/";
+ info->summary = "Simulate an initial negotiation declining excessive streams";
+ info->description =
+ "Initial negotiation tests declining too many streams on the answering side.\n"
+ "After negotiation both offerer and answerer sides should have the same\n"
+ "expected stream types and formats.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ return sdp_negotiation_completed_tests(test,
+ ARRAY_LEN(offerer_formats), offerer_formats,
+ 0, NULL,
+ 0, NULL,
+ 0,
+ validate_aaaa_declined_sdp_streams);
+}
+
+AST_TEST_DEFINE(sdp_negotiation_not_acceptable)
{
enum ast_test_result_state res = AST_TEST_PASS;
struct ast_sdp_state *sdp_state_offerer = NULL;
struct ast_sdp_state *sdp_state_answerer = NULL;
const struct ast_sdp *offerer_sdp;
- const struct ast_sdp *answerer_sdp;
static const struct sdp_format offerer_formats[] = {
- { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },
- { AST_MEDIA_TYPE_AUDIO, "ulaw" },
- { AST_MEDIA_TYPE_VIDEO, "h261" },
- { AST_MEDIA_TYPE_IMAGE, "t38" },
- };
- static const struct sdp_format answerer_formats[] = {
- { AST_MEDIA_TYPE_AUDIO, "ulaw" },
+ { AST_MEDIA_TYPE_AUDIO, "alaw" },
+ { AST_MEDIA_TYPE_AUDIO, "alaw" },
};
switch(cmd) {
case TEST_INIT:
- info->name = "sdp_merge_asymmetric";
+ info->name = "sdp_negotiation_not_acceptable";
info->category = "/main/sdp/";
- info->summary = "Merge two SDPs with an asymmetric number of streams";
+ info->summary = "Simulate an initial negotiation declining all streams";
info->description =
- "SDP 1 offers a four stream topology: Audio,Audio,Video,T.38\n"
- "SDP 2 only has a single audio stream topology\n"
- "We ensure that both local SDPs have the expected stream types and\n"
- "the expected declined streams";
+ "Initial negotiation tests declining all streams for a 488 on the answering side.\n"
+ "Negotiations should fail because there are no acceptable streams.";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
- sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL);
+ sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats,
+ ARRAY_LEN(offerer_formats), offerer_formats, 0, NULL);
if (!sdp_state_offerer) {
res = AST_TEST_FAIL;
goto end;
}
- sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL);
+ sdp_state_answerer = build_sdp_state(0, NULL, 0, NULL, 0, NULL);
if (!sdp_state_answerer) {
res = AST_TEST_FAIL;
goto end;
@@ -1047,24 +1221,15 @@ AST_TEST_DEFINE(sdp_merge_asymmetric)
goto end;
}
- ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp);
- answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer);
- if (!answerer_sdp) {
+ if (!ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp)) {
+ ast_test_status_update(test, "Bad. Setting remote SDP was successful.\n");
res = AST_TEST_FAIL;
goto end;
}
-
- ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp);
-
-#if defined(XXX_TODO_NEED_TO_HANDLE_DECLINED_STREAMS_ON_OFFER_SIDE)
- /* Get the offerer SDP again because it's now going to be the joint SDP */
- offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);
- if (validate_merged_sdp_asymmetric(test, offerer_sdp, 1)) {
- res = AST_TEST_FAIL;
- }
-#endif
- if (validate_merged_sdp_asymmetric(test, answerer_sdp, 0)) {
+ if (!ast_sdp_state_is_offer_rejected(sdp_state_answerer)) {
+ ast_test_status_update(test, "Bad. Negotiation failed for some other reason.\n");
res = AST_TEST_FAIL;
+ goto end;
}
end:
@@ -1135,9 +1300,12 @@ AST_TEST_DEFINE(sdp_ssrc_attributes)
ast_test_status_update(test, "Failed to allocate SDP options\n");
goto end;
}
+ if (build_sdp_option_formats(options, ARRAY_LEN(formats), formats)) {
+ goto end;
+ }
ast_sdp_options_set_ssrc(options, 1);
- test_state = build_sdp_state(ARRAY_LEN(formats), formats, options);
+ test_state = build_sdp_state(ARRAY_LEN(formats), formats, 0, NULL, 0, options);
if (!test_state) {
ast_test_status_update(test, "Failed to create SDP state\n");
goto end;
@@ -1179,6 +1347,725 @@ end:
return res;
}
+struct sdp_topology_stream {
+ /*! Media stream type: audio, video, image */
+ enum ast_media_type type;
+ /*! Media stream state: removed/declined, sendrecv */
+ enum ast_stream_state state;
+ /*! Comma separated list of formats allowed on the stream. Can be NULL if stream is removed/declined. */
+ const char *formats;
+ /*! Optional name of stream. NULL for default name. */
+ const char *name;
+};
+
+struct sdp_update_test {
+ /*! Maximum number of streams. (0 if default) */
+ int max_streams;
+ /*! Optional initial SDP state topology (NULL if not present) */
+ const struct sdp_topology_stream * const *initial;
+ /*! Required first topology update */
+ const struct sdp_topology_stream * const *update_1;
+ /*! Optional second topology update (NULL if not present) */
+ const struct sdp_topology_stream * const *update_2;
+ /*! Expected topology to be offered */
+ const struct sdp_topology_stream * const *expected;
+};
+
+static struct ast_stream_topology *build_update_topology(const struct sdp_topology_stream * const *spec)
+{
+ struct ast_stream_topology *topology;
+ const struct sdp_topology_stream *desc;
+
+ topology = ast_stream_topology_alloc();
+ if (!topology) {
+ return NULL;
+ }
+
+ for (desc = *spec; desc; ++spec, desc = *spec) {
+ struct ast_stream *stream;
+ const char *name;
+
+ name = desc->name ?: ast_codec_media_type2str(desc->type);
+ stream = ast_stream_alloc(name, desc->type);
+ if (!stream) {
+ goto fail;
+ }
+ ast_stream_set_state(stream, desc->state);
+ if (desc->formats) {
+ struct ast_format_cap *caps;
+
+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);
+ if (!caps
+ || ast_format_cap_update_by_allow_disallow(caps, desc->formats, 1) < 0) {
+ ao2_cleanup(caps);
+ ast_stream_free(stream);
+ goto fail;
+ }
+ ast_stream_set_formats(stream, caps);
+ ao2_ref(caps, -1);
+ }
+ if (ast_stream_topology_append_stream(topology, stream) < 0) {
+ ast_stream_free(stream);
+ goto fail;
+ }
+ }
+ return topology;
+
+fail:
+ ast_stream_topology_free(topology);
+ return NULL;
+}
+
+static int cmp_update_topology(struct ast_test *test,
+ const struct ast_stream_topology *expected, const struct ast_stream_topology *merged)
+{
+ int status = 0;
+ int idx;
+ int max_streams;
+ struct ast_stream *exp_stream;
+ struct ast_stream *mrg_stream;
+
+ idx = ast_stream_topology_get_count(expected);
+ max_streams = ast_stream_topology_get_count(merged);
+ if (idx != max_streams) {
+ ast_test_status_update(test, "Expected %d streams got %d streams\n",
+ idx, max_streams);
+ status = -1;
+ }
+ if (idx < max_streams) {
+ max_streams = idx;
+ }
+
+ /* Compare common streams by position */
+ for (idx = 0; idx < max_streams; ++idx) {
+ exp_stream = ast_stream_topology_get_stream(expected, idx);
+ mrg_stream = ast_stream_topology_get_stream(merged, idx);
+
+ if (strcmp(ast_stream_get_name(exp_stream), ast_stream_get_name(mrg_stream))) {
+ ast_test_status_update(test,
+ "Stream %d: Expected stream name '%s' got stream name '%s'\n",
+ idx,
+ ast_stream_get_name(exp_stream),
+ ast_stream_get_name(mrg_stream));
+ status = -1;
+ }
+
+ if (ast_stream_get_state(exp_stream) != ast_stream_get_state(mrg_stream)) {
+ ast_test_status_update(test,
+ "Stream %d: Expected stream state '%s' got stream state '%s'\n",
+ idx,
+ ast_stream_state2str(ast_stream_get_state(exp_stream)),
+ ast_stream_state2str(ast_stream_get_state(mrg_stream)));
+ status = -1;
+ }
+
+ if (ast_stream_get_type(exp_stream) != ast_stream_get_type(mrg_stream)) {
+ ast_test_status_update(test,
+ "Stream %d: Expected stream type '%s' got stream type '%s'\n",
+ idx,
+ ast_codec_media_type2str(ast_stream_get_type(exp_stream)),
+ ast_codec_media_type2str(ast_stream_get_type(mrg_stream)));
+ status = -1;
+ continue;
+ }
+
+ if (ast_stream_get_state(exp_stream) == AST_STREAM_STATE_REMOVED
+ || ast_stream_get_state(mrg_stream) == AST_STREAM_STATE_REMOVED) {
+ /*
+ * Cannot compare formats if one of the streams is
+ * declined because there may not be any on the declined
+ * stream.
+ */
+ continue;
+ }
+ if (!ast_format_cap_identical(ast_stream_get_formats(exp_stream),
+ ast_stream_get_formats(mrg_stream))) {
+ ast_test_status_update(test,
+ "Stream %d: Expected formats do not match merged formats\n",
+ idx);
+ status = -1;
+ }
+ }
+
+ return status;
+}
+
+
+static const struct sdp_topology_stream audio_declined_no_name = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_REMOVED, NULL, NULL
+};
+
+static const struct sdp_topology_stream audio_ulaw_no_name = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", NULL
+};
+
+static const struct sdp_topology_stream audio_alaw_no_name = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", NULL
+};
+
+static const struct sdp_topology_stream audio_g722_no_name = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", NULL
+};
+
+static const struct sdp_topology_stream audio_g723_no_name = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g723", NULL
+};
+
+static const struct sdp_topology_stream video_declined_no_name = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, NULL, NULL
+};
+
+static const struct sdp_topology_stream video_h261_no_name = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", NULL
+};
+
+static const struct sdp_topology_stream video_h263_no_name = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", NULL
+};
+
+static const struct sdp_topology_stream video_h264_no_name = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", NULL
+};
+
+static const struct sdp_topology_stream video_vp8_no_name = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "vp8", NULL
+};
+
+static const struct sdp_topology_stream image_declined_no_name = {
+ AST_MEDIA_TYPE_IMAGE, AST_STREAM_STATE_REMOVED, NULL, NULL
+};
+
+static const struct sdp_topology_stream image_t38_no_name = {
+ AST_MEDIA_TYPE_IMAGE, AST_STREAM_STATE_SENDRECV, "t38", NULL
+};
+
+
+static const struct sdp_topology_stream *top_ulaw_alaw_h264__vp8[] = {
+ &audio_ulaw_no_name,
+ &audio_alaw_no_name,
+ &video_h264_no_name,
+ &video_vp8_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top__vp8_alaw_h264_ulaw[] = {
+ &video_vp8_no_name,
+ &audio_alaw_no_name,
+ &video_h264_no_name,
+ &audio_ulaw_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_alaw_ulaw__vp8_h264[] = {
+ &audio_alaw_no_name,
+ &audio_ulaw_no_name,
+ &video_vp8_no_name,
+ &video_h264_no_name,
+ NULL
+};
+
+/* Sorting by type with no new or deleted streams */
+static const struct sdp_update_test mrg_by_type_00 = {
+ .initial = top_ulaw_alaw_h264__vp8,
+ .update_1 = top__vp8_alaw_h264_ulaw,
+ .expected = top_alaw_ulaw__vp8_h264,
+};
+
+
+static const struct sdp_topology_stream *top_alaw__vp8[] = {
+ &audio_alaw_no_name,
+ &video_vp8_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_h264__vp8_ulaw[] = {
+ &video_h264_no_name,
+ &video_vp8_no_name,
+ &audio_ulaw_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_ulaw_h264__vp8[] = {
+ &audio_ulaw_no_name,
+ &video_h264_no_name,
+ &video_vp8_no_name,
+ NULL
+};
+
+/* Sorting by type and adding a stream */
+static const struct sdp_update_test mrg_by_type_01 = {
+ .initial = top_alaw__vp8,
+ .update_1 = top_h264__vp8_ulaw,
+ .expected = top_ulaw_h264__vp8,
+};
+
+
+static const struct sdp_topology_stream *top_alaw__vp8_vdec[] = {
+ &audio_alaw_no_name,
+ &video_vp8_no_name,
+ &video_declined_no_name,
+ NULL
+};
+
+/* Sorting by type and deleting a stream */
+static const struct sdp_update_test mrg_by_type_02 = {
+ .initial = top_ulaw_h264__vp8,
+ .update_1 = top_alaw__vp8,
+ .expected = top_alaw__vp8_vdec,
+};
+
+
+static const struct sdp_topology_stream *top_h264_alaw_ulaw[] = {
+ &video_h264_no_name,
+ &audio_alaw_no_name,
+ &audio_ulaw_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top__t38[] = {
+ &image_t38_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_vdec__t38_adec[] = {
+ &video_declined_no_name,
+ &image_t38_no_name,
+ &audio_declined_no_name,
+ NULL
+};
+
+/* Sorting by type changing stream types for T.38 */
+static const struct sdp_update_test mrg_by_type_03 = {
+ .initial = top_h264_alaw_ulaw,
+ .update_1 = top__t38,
+ .expected = top_vdec__t38_adec,
+};
+
+
+/* Sorting by type changing stream types back from T.38 */
+static const struct sdp_update_test mrg_by_type_04 = {
+ .initial = top_vdec__t38_adec,
+ .update_1 = top_h264_alaw_ulaw,
+ .expected = top_h264_alaw_ulaw,
+};
+
+
+static const struct sdp_topology_stream *top_h264[] = {
+ &video_h264_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_vdec__t38[] = {
+ &video_declined_no_name,
+ &image_t38_no_name,
+ NULL
+};
+
+/* Sorting by type changing stream types for T.38 */
+static const struct sdp_update_test mrg_by_type_05 = {
+ .initial = top_h264,
+ .update_1 = top__t38,
+ .expected = top_vdec__t38,
+};
+
+
+static const struct sdp_topology_stream *top_h264_idec[] = {
+ &video_h264_no_name,
+ &image_declined_no_name,
+ NULL
+};
+
+/* Sorting by type changing stream types back from T.38 */
+static const struct sdp_update_test mrg_by_type_06 = {
+ .initial = top_vdec__t38,
+ .update_1 = top_h264,
+ .expected = top_h264_idec,
+};
+
+
+static const struct sdp_topology_stream *top_ulaw_adec_h264__vp8[] = {
+ &audio_ulaw_no_name,
+ &audio_declined_no_name,
+ &video_h264_no_name,
+ &video_vp8_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_h263_alaw_h261_h264_vp8[] = {
+ &video_h263_no_name,
+ &audio_alaw_no_name,
+ &video_h261_no_name,
+ &video_h264_no_name,
+ &video_vp8_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_alaw_h264_h263_h261_vp8[] = {
+ &audio_alaw_no_name,
+ &video_h264_no_name,
+ &video_h263_no_name,
+ &video_h261_no_name,
+ &video_vp8_no_name,
+ NULL
+};
+
+/* Sorting by type with backfill and adding streams */
+static const struct sdp_update_test mrg_by_type_07 = {
+ .initial = top_ulaw_adec_h264__vp8,
+ .update_1 = top_h263_alaw_h261_h264_vp8,
+ .expected = top_alaw_h264_h263_h261_vp8,
+};
+
+
+static const struct sdp_topology_stream *top_ulaw_alaw_h264__vp8_h261[] = {
+ &audio_ulaw_no_name,
+ &audio_alaw_no_name,
+ &video_h264_no_name,
+ &video_vp8_no_name,
+ &video_h261_no_name,
+ NULL
+};
+
+/* Sorting by type overlimit of 4 and drop */
+static const struct sdp_update_test mrg_by_type_08 = {
+ .max_streams = 4,
+ .initial = top_ulaw_alaw_h264__vp8,
+ .update_1 = top_ulaw_alaw_h264__vp8_h261,
+ .expected = top_ulaw_alaw_h264__vp8,
+};
+
+
+static const struct sdp_topology_stream *top_ulaw_alaw_h264[] = {
+ &audio_ulaw_no_name,
+ &audio_alaw_no_name,
+ &video_h264_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_alaw_h261__vp8[] = {
+ &audio_alaw_no_name,
+ &video_h261_no_name,
+ &video_vp8_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_alaw_adec_h261__vp8[] = {
+ &audio_alaw_no_name,
+ &audio_declined_no_name,
+ &video_h261_no_name,
+ &video_vp8_no_name,
+ NULL
+};
+
+/* Sorting by type with delete and add of streams */
+static const struct sdp_update_test mrg_by_type_09 = {
+ .initial = top_ulaw_alaw_h264,
+ .update_1 = top_alaw_h261__vp8,
+ .expected = top_alaw_adec_h261__vp8,
+};
+
+
+static const struct sdp_topology_stream *top_ulaw_adec_h264[] = {
+ &audio_ulaw_no_name,
+ &audio_declined_no_name,
+ &video_h264_no_name,
+ NULL
+};
+
+/* Sorting by type and adding streams */
+static const struct sdp_update_test mrg_by_type_10 = {
+ .initial = top_ulaw_adec_h264,
+ .update_1 = top_alaw_ulaw__vp8_h264,
+ .expected = top_alaw_ulaw__vp8_h264,
+};
+
+
+static const struct sdp_topology_stream *top_adec_g722_h261[] = {
+ &audio_declined_no_name,
+ &audio_g722_no_name,
+ &video_h261_no_name,
+ NULL
+};
+
+/* Sorting by type and deleting old streams */
+static const struct sdp_update_test mrg_by_type_11 = {
+ .initial = top_ulaw_alaw_h264,
+ .update_1 = top_adec_g722_h261,
+ .expected = top_adec_g722_h261,
+};
+
+
+static const struct sdp_topology_stream audio_alaw4dave = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", "dave"
+};
+
+static const struct sdp_topology_stream audio_g7224dave = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", "dave"
+};
+
+static const struct sdp_topology_stream audio_ulaw4fred = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", "fred"
+};
+
+static const struct sdp_topology_stream audio_alaw4fred = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", "fred"
+};
+
+static const struct sdp_topology_stream audio_ulaw4rose = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", "rose"
+};
+
+static const struct sdp_topology_stream audio_g7224rose = {
+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", "rose"
+};
+
+
+static const struct sdp_topology_stream video_h2614dave = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", "dave"
+};
+
+static const struct sdp_topology_stream video_h2634dave = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", "dave"
+};
+
+static const struct sdp_topology_stream video_h2634fred = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", "fred"
+};
+
+static const struct sdp_topology_stream video_h2644fred = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", "fred"
+};
+
+static const struct sdp_topology_stream video_h2644rose = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", "rose"
+};
+
+static const struct sdp_topology_stream video_h2614rose = {
+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", "rose"
+};
+
+
+static const struct sdp_topology_stream *top_adave_alaw_afred_ulaw_arose_g722_vdave_h261_vfred_h263_vrose_h264[] = {
+ &audio_alaw4dave,
+ &audio_alaw_no_name,
+ &audio_ulaw4fred,
+ &audio_ulaw_no_name,
+ &audio_g7224rose,
+ &audio_g722_no_name,
+ &video_h2614dave,
+ &video_h261_no_name,
+ &video_h2634fred,
+ &video_h263_no_name,
+ &video_h2644rose,
+ &video_h264_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_vfred_vrose_vdave_h263_h264_h261_afred_ulaw_arose_g722_adave_alaw[] = {
+ &video_h2644fred,
+ &video_h2614rose,
+ &video_h2634dave,
+ &video_h263_no_name,
+ &video_h264_no_name,
+ &video_h261_no_name,
+ &audio_alaw4fred,
+ &audio_ulaw_no_name,
+ &audio_ulaw4rose,
+ &audio_g722_no_name,
+ &audio_g7224dave,
+ &audio_alaw_no_name,
+ NULL
+};
+
+static const struct sdp_topology_stream *top_adave_ulaw_afred_g722_arose_alaw_vdave_h263_vfred_h264_vrose_h261[] = {
+ &audio_g7224dave,
+ &audio_ulaw_no_name,
+ &audio_alaw4fred,
+ &audio_g722_no_name,
+ &audio_ulaw4rose,
+ &audio_alaw_no_name,
+ &video_h2634dave,
+ &video_h263_no_name,
+ &video_h2644fred,
+ &video_h264_no_name,
+ &video_h2614rose,
+ &video_h261_no_name,
+ NULL
+};
+
+/* Sorting by name and type with no new or deleted streams */
+static const struct sdp_update_test mrg_by_name_00 = {
+ .initial = top_adave_alaw_afred_ulaw_arose_g722_vdave_h261_vfred_h263_vrose_h264,
+ .update_1 = top_vfred_vrose_vdave_h263_h264_h261_afred_ulaw_arose_g722_adave_alaw,
+ .expected = top_adave_ulaw_afred_g722_arose_alaw_vdave_h263_vfred_h264_vrose_h261,
+};
+
+
+static const struct sdp_topology_stream *top_adave_g723_h261[] = {
+ &audio_g7224dave,
+ &audio_g723_no_name,
+ &video_h261_no_name,
+ NULL
+};
+
+/* Sorting by name and type adding names to streams */
+static const struct sdp_update_test mrg_by_name_01 = {
+ .initial = top_ulaw_alaw_h264,
+ .update_1 = top_adave_g723_h261,
+ .expected = top_adave_g723_h261,
+};
+
+
+/* Sorting by name and type removing names from streams */
+static const struct sdp_update_test mrg_by_name_02 = {
+ .initial = top_adave_g723_h261,
+ .update_1 = top_ulaw_alaw_h264,
+ .expected = top_ulaw_alaw_h264,
+};
+
+
+static const struct sdp_update_test *sdp_update_cases[] = {
+ /* Merging by type */
+ /* 00 */ &mrg_by_type_00,
+ /* 01 */ &mrg_by_type_01,
+ /* 02 */ &mrg_by_type_02,
+ /* 03 */ &mrg_by_type_03,
+ /* 04 */ &mrg_by_type_04,
+ /* 05 */ &mrg_by_type_05,
+ /* 06 */ &mrg_by_type_06,
+ /* 07 */ &mrg_by_type_07,
+ /* 08 */ &mrg_by_type_08,
+ /* 09 */ &mrg_by_type_09,
+ /* 10 */ &mrg_by_type_10,
+ /* 11 */ &mrg_by_type_11,
+
+ /* Merging by name and type */
+ /* 12 */ &mrg_by_name_00,
+ /* 13 */ &mrg_by_name_01,
+ /* 14 */ &mrg_by_name_02,
+};
+
+AST_TEST_DEFINE(sdp_update_topology)
+{
+ enum ast_test_result_state res;
+ unsigned int idx;
+ int status;
+ struct ast_sdp_options *options;
+ struct ast_stream_topology *topology;
+ struct ast_sdp_state *test_state = NULL;
+
+ static const struct sdp_format sdp_formats[] = {
+ { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,g723" },
+ { AST_MEDIA_TYPE_VIDEO, "h261,h263,h264,vp8" },
+ { AST_MEDIA_TYPE_IMAGE, "t38" },
+ };
+
+ switch(cmd) {
+ case TEST_INIT:
+ info->name = "sdp_update_topology";
+ info->category = "/main/sdp/";
+ info->summary = "Merge topology updates from the system";
+ info->description =
+ "1) Create a SDP state with an optional initial topology.\n"
+ "2) Update the initial topology with one or two new topologies.\n"
+ "3) Get the SDP offer to merge the updates into the initial topology.\n"
+ "4) Check that the offered topology matches the expected topology.\n"
+ "5) Repeat these steps for each test case defined.";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ res = AST_TEST_FAIL;
+ for (idx = 0; idx < ARRAY_LEN(sdp_update_cases); ++idx) {
+ ast_test_status_update(test, "Starting update case %d\n", idx);
+
+ /* Create a SDP state with an optional initial topology. */
+ options = sdp_options_common();
+ if (!options) {
+ ast_test_status_update(test, "Failed to allocate SDP options\n");
+ goto end;
+ }
+ if (sdp_update_cases[idx]->max_streams) {
+ ast_sdp_options_set_max_streams(options, sdp_update_cases[idx]->max_streams);
+ }
+ if (build_sdp_option_formats(options, ARRAY_LEN(sdp_formats), sdp_formats)) {
+ ast_test_status_update(test, "Failed to setup SDP options new stream formats\n");
+ goto end;
+ }
+ if (sdp_update_cases[idx]->initial) {
+ topology = build_update_topology(sdp_update_cases[idx]->initial);
+ if (!topology) {
+ ast_test_status_update(test, "Failed to build initial SDP state topology\n");
+ goto end;
+ }
+ } else {
+ topology = NULL;
+ }
+ test_state = ast_sdp_state_alloc(topology, options);
+ ast_stream_topology_free(topology);
+ if (!test_state) {
+ ast_test_status_update(test, "Failed to build SDP state\n");
+ goto end;
+ }
+
+ /* Update the initial topology with one or two new topologies. */
+ topology = build_update_topology(sdp_update_cases[idx]->update_1);
+ if (!topology) {
+ ast_test_status_update(test, "Failed to build first update SDP state topology\n");
+ goto end;
+ }
+ status = ast_sdp_state_update_local_topology(test_state, topology);
+ ast_stream_topology_free(topology);
+ if (status) {
+ ast_test_status_update(test, "Failed to update first update SDP state topology\n");
+ goto end;
+ }
+ if (sdp_update_cases[idx]->update_2) {
+ topology = build_update_topology(sdp_update_cases[idx]->update_2);
+ if (!topology) {
+ ast_test_status_update(test, "Failed to build second update SDP state topology\n");
+ goto end;
+ }
+ status = ast_sdp_state_update_local_topology(test_state, topology);
+ ast_stream_topology_free(topology);
+ if (status) {
+ ast_test_status_update(test, "Failed to update second update SDP state topology\n");
+ goto end;
+ }
+ }
+
+ /* Get the SDP offer to merge the updates into the initial topology. */
+ if (!ast_sdp_state_get_local_sdp(test_state)) {
+ ast_test_status_update(test, "Failed to create offer SDP\n");
+ goto end;
+ }
+
+ /* Check that the offered topology matches the expected topology. */
+ topology = build_update_topology(sdp_update_cases[idx]->expected);
+ if (!topology) {
+ ast_test_status_update(test, "Failed to build expected topology\n");
+ goto end;
+ }
+ status = cmp_update_topology(test, topology,
+ ast_sdp_state_get_local_topology(test_state));
+ ast_stream_topology_free(topology);
+ if (status) {
+ ast_test_status_update(test, "Failed to match expected topology\n");
+ goto end;
+ }
+
+ /* Repeat for each test case defined. */
+ ast_sdp_state_free(test_state);
+ test_state = NULL;
+ }
+ res = AST_TEST_PASS;
+
+end:
+ ast_sdp_state_free(test_state);
+ return res;
+}
+
static int unload_module(void)
{
AST_TEST_UNREGISTER(invalid_rtpmap);
@@ -1186,10 +2073,13 @@ static int unload_module(void)
AST_TEST_UNREGISTER(find_attr);
AST_TEST_UNREGISTER(topology_to_sdp);
AST_TEST_UNREGISTER(sdp_to_topology);
- AST_TEST_UNREGISTER(sdp_merge_symmetric);
- AST_TEST_UNREGISTER(sdp_merge_crisscross);
- AST_TEST_UNREGISTER(sdp_merge_asymmetric);
+ AST_TEST_UNREGISTER(sdp_negotiation_initial);
+ AST_TEST_UNREGISTER(sdp_negotiation_type_change);
+ AST_TEST_UNREGISTER(sdp_negotiation_decline_incompatible);
+ AST_TEST_UNREGISTER(sdp_negotiation_decline_max_streams);
+ AST_TEST_UNREGISTER(sdp_negotiation_not_acceptable);
AST_TEST_UNREGISTER(sdp_ssrc_attributes);
+ AST_TEST_UNREGISTER(sdp_update_topology);
return 0;
}
@@ -1201,10 +2091,13 @@ static int load_module(void)
AST_TEST_REGISTER(find_attr);
AST_TEST_REGISTER(topology_to_sdp);
AST_TEST_REGISTER(sdp_to_topology);
- AST_TEST_REGISTER(sdp_merge_symmetric);
- AST_TEST_REGISTER(sdp_merge_crisscross);
- AST_TEST_REGISTER(sdp_merge_asymmetric);
+ AST_TEST_REGISTER(sdp_negotiation_initial);
+ AST_TEST_REGISTER(sdp_negotiation_type_change);
+ AST_TEST_REGISTER(sdp_negotiation_decline_incompatible);
+ AST_TEST_REGISTER(sdp_negotiation_decline_max_streams);
+ AST_TEST_REGISTER(sdp_negotiation_not_acceptable);
AST_TEST_REGISTER(sdp_ssrc_attributes);
+ AST_TEST_REGISTER(sdp_update_topology);
return AST_MODULE_LOAD_SUCCESS;
}
diff --git a/third-party/configure.m4 b/third-party/configure.m4
index 635446638..55b72daf9 100644
--- a/third-party/configure.m4
+++ b/third-party/configure.m4
@@ -1,4 +1,7 @@
-
+#
+# If this file is changed, be sure to run ASTTOPDIR/bootstrap.sh
+# before committing.
+#
AC_DEFUN([THIRD_PARTY_CONFIGURE],
[
diff --git a/third-party/pjproject/Makefile.rules b/third-party/pjproject/Makefile.rules
index 3f99c8a8f..acd766218 100644
--- a/third-party/pjproject/Makefile.rules
+++ b/third-party/pjproject/Makefile.rules
@@ -1,8 +1,11 @@
PJPROJECT_URL ?= https://raw.githubusercontent.com/asterisk/third-party/master/pjproject/$(PJPROJECT_VERSION)
+# PJPROJECT_CONFIGURE_OPTS could come from the command line or could be
+# set/modified by configure.m4 if the build or host tuples aren't the same
+# as the current build environment (cross-compile).
# Even though we're not installing pjproject, we're setting prefix to /opt/pjproject to be safe
-PJPROJECT_CONFIG_OPTS = --prefix=/opt/pjproject \
+PJPROJECT_CONFIG_OPTS = $(PJPROJECT_CONFIGURE_OPTS) --prefix=/opt/pjproject \
--disable-speex-codec \
--disable-speex-aec \
--disable-speex-aec \
diff --git a/third-party/pjproject/configure.m4 b/third-party/pjproject/configure.m4
index a5e9fca60..709a706a1 100644
--- a/third-party/pjproject/configure.m4
+++ b/third-party/pjproject/configure.m4
@@ -1,3 +1,8 @@
+#
+# If this file is changed, be sure to run ASTTOPDIR/bootstrap.sh
+# before committing.
+#
+
AC_DEFUN([_PJPROJECT_CONFIGURE],
[
if test "${ac_mandatory_list#*PJPROJECT*}" != "$ac_mandatory_list" ; then
@@ -35,17 +40,30 @@ AC_DEFUN([_PJPROJECT_CONFIGURE],
AC_MSG_ERROR(cat is required to build bundled pjproject)
fi
+ AC_ARG_VAR([PJPROJECT_CONFIGURE_OPTS],[Additional configure options to pass to bundled pjproject])
+ this_host=$(./config.sub $(./config.guess))
+ if test "$build" != "$this_host" ; then
+ PJPROJECT_CONFIGURE_OPTS+=" --build=$build"
+ fi
+ if test "$host" != "$this_host" ; then
+ PJPROJECT_CONFIGURE_OPTS+=" --host=$host"
+ fi
+
export TAR PATCH SED NM EXTERNALS_CACHE_DIR DOWNLOAD_TO_STDOUT DOWNLOAD_TIMEOUT DOWNLOAD MD5 CAT
- ${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} EXTERNALS_CACHE_DIR=${EXTERNALS_CACHE_DIR} configure
+ export NOISY_BUILD
+ ${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} \
+ PJPROJECT_CONFIGURE_OPTS="$PJPROJECT_CONFIGURE_OPTS" \
+ EXTERNALS_CACHE_DIR="${EXTERNALS_CACHE_DIR}" \
+ configure
if test $? -ne 0 ; then
AC_MSG_RESULT(failed)
AC_MSG_NOTICE(Unable to configure ${PJPROJECT_DIR})
- AC_MSG_ERROR(Run "${GNU_MAKE} -C ${PJPROJECT_DIR} NOISY_BUILD=yes configure" to see error details.)
+ AC_MSG_ERROR(Re-run the ./configure command with 'NOISY_BUILD=yes' appended to see error details.)
fi
AC_MSG_CHECKING(for bundled pjproject)
- PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} EXTERNALS_CACHE_DIR=${EXTERNALS_CACHE_DIR} echo_cflags)
+ PJPROJECT_INCLUDE=$(${GNU_MAKE} --quiet --no-print-directory -C ${PJPROJECT_DIR} PJPROJECT_CONFIGURE_OPTS="$PJPROJECT_CONFIGURE_OPTS" EXTERNALS_CACHE_DIR="${EXTERNALS_CACHE_DIR}" echo_cflags)
PJPROJECT_CFLAGS="$PJPROJECT_INCLUDE"
PBX_PJPROJECT=1
diff --git a/third-party/pjproject/patches/0070-Set-PJSIP_INV_SUPPORT_UPDATE-correctly-in-pjsip_inv_.patch b/third-party/pjproject/patches/0070-Set-PJSIP_INV_SUPPORT_UPDATE-correctly-in-pjsip_inv_.patch
new file mode 100644
index 000000000..9238e3ec9
--- /dev/null
+++ b/third-party/pjproject/patches/0070-Set-PJSIP_INV_SUPPORT_UPDATE-correctly-in-pjsip_inv_.patch
@@ -0,0 +1,29 @@
+From 1193681959816effa121c4470748d5faa3a59272 Mon Sep 17 00:00:00 2001
+From: George Joseph <gjoseph@digium.com>
+Date: Thu, 29 Jun 2017 13:42:10 -0600
+Subject: [PATCH] Set PJSIP_INV_SUPPORT_UPDATE correctly in
+ pjsip_inv_verify_request3
+
+pjsip_inv_verify_request3 was setting rem_options when UPDATE was
+detected in the Allow header. That's just an internal variable and
+doesn't go anywhere. It's '*options' that needs to be set.
+---
+ pjsip/src/pjsip-ua/sip_inv.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/pjsip/src/pjsip-ua/sip_inv.c b/pjsip/src/pjsip-ua/sip_inv.c
+index fbc8ebe..6db7e6b 100644
+--- a/pjsip/src/pjsip-ua/sip_inv.c
++++ b/pjsip/src/pjsip-ua/sip_inv.c
+@@ -1237,7 +1237,7 @@ PJ_DEF(pj_status_t) pjsip_inv_verify_request3(pjsip_rx_data *rdata,
+
+ if (i != allow->count) {
+ /* UPDATE is present in Allow */
+- rem_option |= PJSIP_INV_SUPPORT_UPDATE;
++ *options |= PJSIP_INV_SUPPORT_UPDATE;
+ }
+
+ }
+--
+2.9.4
+
diff --git a/third-party/pjproject/patches/config_site.h b/third-party/pjproject/patches/config_site.h
index a345734b0..561b3a231 100644
--- a/third-party/pjproject/patches/config_site.h
+++ b/third-party/pjproject/patches/config_site.h
@@ -68,7 +68,7 @@
Enabling it will result in SEGFAULTS when URIs containing escape sequences are encountered.
*/
#undef PJSIP_UNESCAPE_IN_PLACE
-#define PJSIP_MAX_PKT_LEN 6000
+#define PJSIP_MAX_PKT_LEN 32000
#undef PJ_TODO
#define PJ_TODO(x)