diff options
114 files changed, 9684 insertions, 8798 deletions
@@ -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 ; ----------------------------------------------------------------------------- ; @@ -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, ×tamp); -} - -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(¤t_path, 0, "%s/%s", ast_str_buffer(path), name); - } else { - ast_str_set(¤t_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(¤t_path, 0, "%s.%s", ast_str_buffer(path), name); - } else { - ast_str_set(¤t_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(¤t_vect, current_idx)) { + return NULL; + } + + if (sdp_vect_idx_init(&update_vect, ast_stream_topology_get_count(update_topology))) { + AST_VECTOR_FREE(¤t_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(¤t_vect); current_idx--;) { + idx = AST_VECTOR_GET(¤t_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(¤t_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, + ¤t_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, + ¤t_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(¤t_vect); current_idx--;) { + idx = AST_VECTOR_GET(¤t_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(¤t_vect); + AST_VECTOR_FREE(&update_vect); + return merged_topology; + +fail: + ast_stream_topology_free(merged_topology); + AST_VECTOR_FREE(¤t_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(¤t->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&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(¬e, 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, ¶meters, 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, ¶meters, 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) |