res_pjsip_session.c: Fix build when TEST_FRAMEWORK is not defined master
authorSean Bright <sean.bright@gmail.com>
Tue, 15 Sep 2020 21:16:34 +0000 (17:16 -0400)
committerGeorge Joseph <gjoseph@digium.com>
Wed, 16 Sep 2020 14:45:45 +0000 (09:45 -0500)
Change-Id: Id4852c26e9c412af8e37b5dd3c15da9453ad3276

54 files changed:
Makefile
apps/app_dial.c
apps/app_queue.c
apps/app_voicemail.c
bridges/bridge_softmix.c
channels/chan_pjsip.c
channels/chan_sip.c
channels/pjsip/dialplan_functions.c
configs/samples/logger.conf.sample
configs/samples/pjsip.conf.sample
contrib/ast-db-manage/config/versions/1ae0609b6646_increse_reg_server_size.py [new file with mode: 0644]
contrib/ast-db-manage/config/versions/b80485ff4dd0_add_pjsip_endpoint_acn_options.py
contrib/ast-db-manage/config/versions/e658c26033ca_create_history_info_flag.py [new file with mode: 0644]
contrib/scripts/ast_coredumper
contrib/scripts/sip_nat_settings
doc/CHANGES-staging/logger_format.txt [new file with mode: 0644]
doc/CHANGES-staging/pjsip_send_session_refresh.txt [new file with mode: 0644]
include/asterisk/bridge_channel.h
include/asterisk/channel.h
include/asterisk/conversions.h
include/asterisk/format_cache.h
include/asterisk/frame.h
include/asterisk/logger.h
include/asterisk/res_pjsip.h
include/asterisk/res_pjsip_session.h
include/asterisk/stream.h
include/asterisk/utils.h
include/asterisk/vector.h
main/bridge.c
main/bridge_channel.c
main/channel.c
main/conversions.c
main/features.c
main/format_cache.c
main/format_cap.c
main/frame.c
main/logger.c
main/pbx.c
main/stream.c
main/utils.c
res/ari/resource_bridges.h
res/parking/parking_bridge_features.c
res/res_musiconhold.c
res/res_pjsip.c
res/res_pjsip/pjsip_configuration.c
res/res_pjsip_diversion.c
res/res_pjsip_session.c
res/res_speech.c
res/res_stasis.c
res/res_stir_shaken/curl.c
res/stasis/stasis_bridge.c
rest-api/api-docs/bridges.json
tests/test_conversions.c
third-party/pjproject/patches/0060-clone-sdp-for-sip-timer-refresh-invite.patch [new file with mode: 0644]

index 3d11ccd..dd1feed 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -249,7 +249,7 @@ else
 endif
 ifneq ($(AWK),)
   ifneq ($(wildcard .version),)
-    ASTERISKVERSIONNUM:=$(shell $(AWK) -F. '{printf "%01d%02d%02d", $$1, $$2, $$3}' .version)
+    ASTERISKVERSIONNUM:=$(shell $(SED) -e 's/^certified\///' -e 's/-cert/./' .version | $(AWK) -F. '{printf "%01d%02d%02d", $$1, $$2, $$3}')
   endif
 endif
 
index 95f36d7..e384b6d 100644 (file)
@@ -1204,7 +1204,8 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
        struct privacy_args *pa,
        const struct cause_args *num_in, int *result, char *dtmf_progress,
        const int ignore_cc,
-       struct ast_party_id *forced_clid, struct ast_party_id *stored_clid)
+       struct ast_party_id *forced_clid, struct ast_party_id *stored_clid,
+       struct ast_bridge_config *config)
 {
        struct cause_args num = *num_in;
        int prestart = num.busy + num.congestion + num.nochan;
@@ -1223,7 +1224,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
        int sent_ring = 0;
        int sent_progress = 0;
        struct timeval start = ast_tvnow();
-       SCOPE_TRACE(1, "%s\n", ast_channel_name(in));
+       SCOPE_ENTER(3, "%s\n", ast_channel_name(in));
 
        if (single) {
                /* Turn off hold music, etc */
@@ -1239,7 +1240,8 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                *to = -1;
                                strcpy(pa->status, "CONGESTION");
                                ast_channel_publish_dial(in, outgoing->chan, NULL, pa->status);
-                               return NULL;
+                               SCOPE_EXIT_RTN_VALUE(NULL, "%s: can't be made compat with %s\n",
+                                       ast_channel_name(in), ast_channel_name(outgoing->chan));
                        }
                }
 
@@ -1281,7 +1283,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                        if (is_cc_recall) {
                                ast_cc_failed(cc_recall_core_id, "Everyone is busy/congested for the recall. How sad");
                        }
-                       return NULL;
+                       SCOPE_EXIT_RTN_VALUE(NULL, "%s: No outging channels available\n", ast_channel_name(in));
                }
                winner = ast_waitfor_n(watchers, pos, to);
                AST_LIST_TRAVERSE(out_chans, o, node) {
@@ -1390,7 +1392,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                case AST_CONTROL_ANSWER:
                                        /* This is our guy if someone answered. */
                                        if (!peer) {
-                                               ast_trace(1, "%s answered %s\n", ast_channel_name(c), ast_channel_name(in));
+                                               ast_trace(-1, "%s answered %s\n", ast_channel_name(c), ast_channel_name(in));
                                                ast_verb(3, "%s answered %s\n", ast_channel_name(c), ast_channel_name(in));
                                                if (o->orig_chan_name
                                                        && strcmp(o->orig_chan_name, ast_channel_name(c))) {
@@ -1418,6 +1420,18 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                                        }
                                                }
                                                peer = c;
+                                               /* Answer can optionally include a topology */
+                                               if (f->subclass.topology) {
+                                                       /*
+                                                        * We need to bump the refcount on the topology to prevent it
+                                                        * from being cleaned up when the frame is cleaned up.
+                                                        */
+                                                       config->answer_topology = ao2_bump(f->subclass.topology);
+                                                       ast_trace(-1, "%s Found topology in frame: %p %p %s\n",
+                                                               ast_channel_name(peer), f, config->answer_topology,
+                                                               ast_str_tmp(256, ast_stream_topology_to_str(config->answer_topology, &STR_TMP)));
+                                               }
+
                                                /* Inform everyone else that they've been canceled.
                                                 * The dial end event for the peer will be sent out after
                                                 * other Dial options have been handled.
@@ -1708,7 +1722,7 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                if (is_cc_recall) {
                                        ast_cc_completed(in, "CC completed, although the caller hung up (cancelled)");
                                }
-                               return NULL;
+                               SCOPE_EXIT_RTN_VALUE(NULL, "%s: Caller hung up\n", ast_channel_name(in));
                        }
 
                        /* now f is guaranteed non-NULL */
@@ -1728,7 +1742,8 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                                if (is_cc_recall) {
                                                        ast_cc_completed(in, "CC completed, but the caller used DTMF to exit");
                                                }
-                                               return NULL;
+                                               SCOPE_EXIT_RTN_VALUE(NULL, "%s: Caller pressed %c to end call\n",
+                                                       ast_channel_name(in), f->subclass.integer);
                                        }
                                        ast_channel_unlock(in);
                                }
@@ -1743,7 +1758,8 @@ static struct ast_channel *wait_for_answer(struct ast_channel *in,
                                        if (is_cc_recall) {
                                                ast_cc_completed(in, "CC completed, but the caller hung up with DTMF");
                                        }
-                                       return NULL;
+                                       SCOPE_EXIT_RTN_VALUE(NULL, "%s: Caller requested disconnect\n",
+                                               ast_channel_name(in));
                                }
                        }
 
@@ -1849,7 +1865,8 @@ skip_frame:;
        if (is_cc_recall) {
                ast_cc_completed(in, "Recall completed!");
        }
-       return peer;
+       SCOPE_EXIT_RTN_VALUE(peer, "%s: %s%s\n", ast_channel_name(in),
+               peer ? "Answered by " : "No answer", peer ? ast_channel_name(peer) : "");
 }
 
 static int detect_disconnect(struct ast_channel *chan, char code, struct ast_str **featurecode)
@@ -2217,7 +2234,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
        struct dial_head out_chans = AST_LIST_HEAD_NOLOCK_INIT_VALUE; /* list of destinations */
        struct chanlist *outgoing;
        struct chanlist *tmp;
-       struct ast_channel *peer;
+       struct ast_channel *peer = NULL;
        int to; /* timeout */
        struct cause_args num = { chan, 0, 0, 0 };
        int cause;
@@ -2271,7 +2288,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
         */
        struct ast_party_caller caller;
        int max_forwards;
-       SCOPE_TRACE(1, "%s Data: %s\n", ast_channel_name(chan), data);
+       SCOPE_ENTER(1, "%s: Data: %s\n", ast_channel_name(chan), data);
 
        /* Reset all DIAL variables back to blank, to prevent confusion (in case we don't reset all of them). */
        ast_channel_lock(chan);
@@ -2295,7 +2312,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                ast_log(LOG_WARNING, "Cannot place outbound call from channel '%s'. Max forwards exceeded\n",
                                ast_channel_name(chan));
                pbx_builtin_setvar_helper(chan, "DIALSTATUS", "BUSY");
-               return -1;
+               SCOPE_EXIT_RTN_VALUE(-1, "%s: Max forwards exceeded\n", ast_channel_name(chan));
        }
 
        if (ast_check_hangup_locked(chan)) {
@@ -2313,7 +2330,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                 */
                ast_verb(3, "Caller hung up before dial.\n");
                pbx_builtin_setvar_helper(chan, "DIALSTATUS", "CANCEL");
-               return -1;
+               SCOPE_EXIT_RTN_VALUE(-1, "%s: Caller hung up before dial\n", ast_channel_name(chan));
        }
 
        parse = ast_strdupa(data ?: "");
@@ -2838,7 +2855,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
        }
 
        peer = wait_for_answer(chan, &out_chans, &to, peerflags, opt_args, &pa, &num, &result,
-               dtmf_progress, ignore_cc, &forced_clid, &stored_clid);
+               dtmf_progress, ignore_cc, &forced_clid, &stored_clid, &config);
 
        if (!peer) {
                if (result) {
@@ -3267,6 +3284,7 @@ static int dial_exec_full(struct ast_channel *chan, const char *data, struct ast
                                ast_channel_setoption(chan, AST_OPTION_OPRMODE, &oprmode, sizeof(oprmode), 0);
                        }
                        setup_peer_after_bridge_goto(chan, peer, &opts, opt_args);
+
                        res = ast_bridge_call(chan, peer, &config);
                }
        }
@@ -3304,6 +3322,18 @@ out:
        }
 
 done:
+       if (config.answer_topology) {
+               ast_trace(2, "%s Cleaning up topology: %p %s\n",
+                       peer ? ast_channel_name(peer) : "<no channel>", &config.answer_topology,
+                       ast_str_tmp(256, ast_stream_topology_to_str(config.answer_topology, &STR_TMP)));
+
+               /*
+                * At this point, the channel driver that answered should have bumped the
+                * topology refcount for itself.  Here we're cleaning up the reference we added
+                * in wait_for_answer().
+                */
+               ast_stream_topology_free(config.answer_topology);
+       }
        if (config.warning_sound) {
                ast_free((char *)config.warning_sound);
        }
@@ -3314,7 +3344,7 @@ done:
                ast_free((char *)config.start_sound);
        }
        ast_ignore_cc(chan);
-       return res;
+       SCOPE_EXIT_RTN_VALUE(res, "%s: Done\n", ast_channel_name(chan));
 }
 
 static int dial_exec(struct ast_channel *chan, const char *data)
index 19622fd..d94a809 100644 (file)
@@ -3458,7 +3458,7 @@ static void rt_handle_member_record(struct call_queue *q, char *category, struct
                        ast_copy_string(m->rt_uniqueid, rt_uniqueid, sizeof(m->rt_uniqueid));
                        if (paused_str) {
                                m->paused = paused;
-                               if (paused) {
+                               if (paused && m->lastpause == 0) {
                                        time(&m->lastpause); /* XXX: Should this come from realtime? */
                                }
                                ast_devstate_changed(m->paused ? QUEUE_PAUSED_DEVSTATE : QUEUE_UNPAUSED_DEVSTATE,
@@ -5641,6 +5641,7 @@ static int wait_our_turn(struct queue_ent *qe, int ringing, enum queue_result *r
                        int status = 0;
 
                        if ((status = get_member_status(qe->parent, qe->max_penalty, qe->min_penalty, qe->raise_penalty, qe->parent->leavewhenempty, 0))) {
+                               record_abandoned(qe);
                                *reason = QUEUE_LEAVEEMPTY;
                                ast_queue_log(qe->parent->name, ast_channel_uniqueid(qe->chan), "NONE", "EXITEMPTY", "%d|%d|%ld", qe->pos, qe->opos, (long) (time(NULL) - qe->start));
                                res = -1;
index 73813df..cc96a1c 100644 (file)
@@ -6576,8 +6576,6 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
 {
 #ifdef IMAP_STORAGE
        int newmsgs, oldmsgs;
-#else
-       char urgdir[PATH_MAX];
 #endif
        char txtfile[PATH_MAX];
        char tmptxtfile[PATH_MAX];
@@ -6996,6 +6994,14 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
                }
                res = play_record_review(chan, NULL, tmptxtfile, vmu->maxsecs, fmt, 1, vmu, &duration, &sound_duration, NULL, options->record_gain, vms, flag, msg_id, 0);
 
+               /* At this point, either we were instructed to make the message Urgent
+                  by arguments to VoiceMail or during the review process by the person
+                  leaving the message. So we update the directory where we want this
+                  message to go. */
+               if (!strcmp(flag, "Urgent")) {
+                       create_dirpath(dir, sizeof(dir), vmu->context, ext, "Urgent");
+               }
+
                if (txt) {
                        fprintf(txt, "flag=%s\n", flag);
                        if (sound_duration < vmu->minsecs) {
@@ -7076,25 +7082,7 @@ static int leave_voicemail(struct ast_channel *chan, char *ext, struct leave_vm_
                                                        free_user(recip);
                                                }
                                        }
-#ifndef IMAP_STORAGE
-                                       if (!ast_strlen_zero(flag) && !strcmp(flag, "Urgent")) { /* If this is an Urgent message */
-                                               /* Move the message from INBOX to Urgent folder if this is urgent! */
-                                               char sfn[PATH_MAX];
-                                               char dfn[PATH_MAX];
-                                               int x;
-                                               /* It's easier just to try to make it than to check for its existence */
-                                               create_dirpath(urgdir, sizeof(urgdir), vmu->context, ext, "Urgent");
-                                               x = last_message_index(vmu, urgdir) + 1;
-                                               make_file(sfn, sizeof(sfn), dir, msgnum);
-                                               make_file(dfn, sizeof(dfn), urgdir, x);
-                                               ast_debug(5, "Created an Urgent message, moving file from %s to %s.\n", sfn, dfn);
-                                               RENAME(dir, msgnum, vmu->mailbox, vmu->context, urgdir, x, sfn, dfn);
-                                               /* Notification must happen for this new message in Urgent folder, not INBOX */
-                                               ast_copy_string(fn, dfn, sizeof(fn));
-                                               pbx_builtin_setvar_helper(chan, "VM_MESSAGEFILE", fn);
-                                               msgnum = x;
-                                       }
-#endif
+
                                        /* Notification needs to happen after the copy, though. */
                                        if (ast_fileexists(fn, NULL, NULL)) {
 #ifdef IMAP_STORAGE
index 817f8b2..8d2d67c 100644 (file)
@@ -629,6 +629,7 @@ static void sfu_topologies_on_join(struct ast_bridge *bridge,
 
        joiner_video = ast_stream_topology_alloc();
        if (!joiner_video) {
+               ast_log(LOG_ERROR, "%s: Couldn't alloc topology\n", ast_channel_name(joiner->chan));
                return;
        }
 
@@ -642,6 +643,7 @@ static void sfu_topologies_on_join(struct ast_bridge *bridge,
        ast_channel_unlock(joiner->chan);
 
        if (res || !sc->topology) {
+               ast_log(LOG_ERROR, "%s: Couldn't append source streams\n", ast_channel_name(joiner->chan));
                goto cleanup;
        }
 
@@ -655,11 +657,17 @@ static void sfu_topologies_on_join(struct ast_bridge *bridge,
                        ast_channel_get_stream_topology(participant->chan));
                ast_channel_unlock(participant->chan);
                if (res) {
+                       ast_log(LOG_ERROR, "%s/%s: Couldn't append source streams\n",
+                               ast_channel_name(participant->chan), ast_channel_name(joiner->chan));
                        goto cleanup;
                }
        }
 
-       ast_channel_request_stream_topology_change(joiner->chan, sc->topology, NULL);
+       res = ast_channel_request_stream_topology_change(joiner->chan, sc->topology, NULL);
+       if (res) {
+               ast_debug(3, "%s: Couldn't request topology change\n", ast_channel_name(joiner->chan));
+               goto cleanup;
+       }
 
        AST_LIST_TRAVERSE(participants, participant, entry) {
                if (participant == joiner) {
@@ -668,9 +676,16 @@ static void sfu_topologies_on_join(struct ast_bridge *bridge,
 
                sc = participant->tech_pvt;
                if (append_all_streams(sc->topology, joiner_video)) {
+                       ast_log(LOG_ERROR, "%s/%s: Couldn't apopend streams\n",
+                               ast_channel_name(participant->chan), ast_channel_name(joiner->chan));
+                       goto cleanup;
+               }
+               res = ast_channel_request_stream_topology_change(participant->chan, sc->topology, NULL);
+               if (res) {
+                       ast_debug(3, "%s/%s: Couldn't request topology change\n",
+                               ast_channel_name(participant->chan), ast_channel_name(joiner->chan));
                        goto cleanup;
                }
-               ast_channel_request_stream_topology_change(participant->chan, sc->topology, NULL);
        }
 
 cleanup:
@@ -1081,11 +1096,7 @@ static int remove_all_original_streams(struct ast_stream_topology *dest,
                        if (!strcmp(ast_stream_get_name(stream), ast_stream_get_name(original_stream))) {
                                struct ast_stream *removed;
 
-                               /* Since the participant is still going to be in the bridge we
-                                * change the name so that routing does not attempt to route video
-                                * to this stream.
-                                */
-                               removed = ast_stream_clone(stream, "removed");
+                               removed = ast_stream_clone(stream, NULL);
                                if (!removed) {
                                        return -1;
                                }
@@ -2254,10 +2265,13 @@ static void softmix_bridge_stream_sources_update(struct ast_bridge *bridge, stru
        struct ast_stream_topology *added_streams;
        struct ast_bridge_channels_list *participants = &bridge->channels;
        struct ast_bridge_channel *participant;
+       SCOPE_ENTER(3, "%s: OT: %s NT: %s\n", ast_channel_name(bridge_channel->chan),
+               ast_str_tmp(256, ast_stream_topology_to_str(old_topology, &STR_TMP)),
+               ast_str_tmp(256, ast_stream_topology_to_str(new_topology, &STR_TMP)));
 
        added_streams = ast_stream_topology_alloc();
        if (!added_streams) {
-               return;
+               SCOPE_EXIT_LOG(LOG_ERROR, "%s: Couldn't alloc topology\n", ast_channel_name(bridge_channel->chan));
        }
 
        /* We go through the old topology comparing it to the new topology to determine what streams
@@ -2266,19 +2280,24 @@ static void softmix_bridge_stream_sources_update(struct ast_bridge *bridge, stru
         * Added streams are copied into a topology and added to each other participant while for
         * removed streams we merely store their position and mark them as removed later.
         */
+       ast_trace(-1, "%s: Checking for state changes\n", ast_channel_name(bridge_channel->chan));
        for (index = 0; index < ast_stream_topology_get_count(sc->topology) && index < ast_stream_topology_get_count(new_topology); ++index) {
                struct ast_stream *old_stream = ast_stream_topology_get_stream(sc->topology, index);
                struct ast_stream *new_stream = ast_stream_topology_get_stream(new_topology, index);
+               SCOPE_ENTER(4, "%s:   Slot: %d  Old stream: %s  New stream: %s\n",  ast_channel_name(bridge_channel->chan),
+                       index, ast_str_tmp(256, ast_stream_to_str(old_stream, &STR_TMP)),
+                       ast_str_tmp(256, ast_stream_to_str(new_stream, &STR_TMP)));
 
                /* Ignore all streams that don't carry video and streams that are strictly outgoing destination streams */
                if ((ast_stream_get_type(old_stream) != AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) != AST_MEDIA_TYPE_VIDEO) ||
-                       !strncmp(ast_stream_get_name(old_stream), SOFTBRIDGE_VIDEO_DEST_PREFIX,
+                       !strncmp(ast_stream_get_name(new_stream), SOFTBRIDGE_VIDEO_DEST_PREFIX,
                                SOFTBRIDGE_VIDEO_DEST_LEN)) {
-                       continue;
+                       SCOPE_EXIT_EXPR(continue, "%s: Stream %d ignored\n",  ast_channel_name(bridge_channel->chan), index);
                }
 
                if (ast_stream_get_type(old_stream) == AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) != AST_MEDIA_TYPE_VIDEO) {
                        /* If a stream renegotiates from video to non-video then we need to remove it as a source */
+                       ast_trace(-1, "%s: Stream %d added to remove list\n",  ast_channel_name(bridge_channel->chan), index);
                        removed_streams[removed_streams_count++] = index;
                } else if (ast_stream_get_type(old_stream) != AST_MEDIA_TYPE_VIDEO && ast_stream_get_type(new_stream) == AST_MEDIA_TYPE_VIDEO) {
                        if (ast_stream_get_state(new_stream) != AST_STREAM_STATE_REMOVED) {
@@ -2286,11 +2305,14 @@ static void softmix_bridge_stream_sources_update(struct ast_bridge *bridge, stru
                                if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan),
                                                        bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL,
                                                        new_stream, index)) {
-                                       goto cleanup;
+                                       SCOPE_EXIT_EXPR(goto cleanup, "%s: Couldn't append source stream %d:%s\n",  ast_channel_name(bridge_channel->chan),
+                                               index, ast_stream_get_name(new_stream));
                                }
+                               ast_trace(-1, "%s: Stream %d changed from non-video to video\n",  ast_channel_name(bridge_channel->chan), index);
                        }
                } else if (ast_stream_get_state(old_stream) != AST_STREAM_STATE_REMOVED &&
                                ast_stream_get_state(new_stream) != AST_STREAM_STATE_SENDRECV && ast_stream_get_state(new_stream) != AST_STREAM_STATE_RECVONLY) {
+                       ast_trace(-1, "%s: Stream %d added to remove list\n",  ast_channel_name(bridge_channel->chan), index);
                        /* If a stream renegotiates and is removed then we remove it */
                        removed_streams[removed_streams_count++] = index;
                } else if ((ast_stream_get_state(old_stream) == AST_STREAM_STATE_REMOVED || ast_stream_get_state(old_stream) == AST_STREAM_STATE_INACTIVE ||
@@ -2301,9 +2323,14 @@ static void softmix_bridge_stream_sources_update(struct ast_bridge *bridge, stru
                        if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan),
                                                bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL,
                                                new_stream, index)) {
-                               goto cleanup;
+                               SCOPE_EXIT_EXPR(goto cleanup, "%s: Couldn't append source stream %d:%s\n",  ast_channel_name(bridge_channel->chan),
+                                       index, ast_stream_get_name(new_stream));
                        }
+                       ast_trace(-1, "%s: Stream %d:%s changed state from %s to %s\n",  ast_channel_name(bridge_channel->chan),
+                               index, ast_stream_get_name(old_stream), ast_stream_state2str(ast_stream_get_state(old_stream)),
+                               ast_stream_state2str(ast_stream_get_state(new_stream)));
                }
+               SCOPE_EXIT();
        }
 
        /* Any newly added streams that did not take the position of a removed stream
@@ -2311,18 +2338,26 @@ static void softmix_bridge_stream_sources_update(struct ast_bridge *bridge, stru
         * removed from the topology but merely marked as removed we can pick up where we
         * left off when comparing the old and new topologies.
         */
+       ast_trace(-1, "%s: Checking for newly added streams\n", ast_channel_name(bridge_channel->chan));
+
        for (; index < ast_stream_topology_get_count(new_topology); ++index) {
                struct ast_stream *stream = ast_stream_topology_get_stream(new_topology, index);
+               SCOPE_ENTER(4, "%s: Checking stream %d:%s\n",  ast_channel_name(bridge_channel->chan), index,
+                       ast_stream_get_name(stream));
 
                if (!is_video_source(stream)) {
-                       continue;
+                       SCOPE_EXIT_EXPR(continue, "%s: Stream %d:%s is not video source\n",  ast_channel_name(bridge_channel->chan),
+                               index, ast_stream_get_name(stream));
                }
 
                if (append_source_stream(added_streams, ast_channel_name(bridge_channel->chan),
                                        bridge->softmix.send_sdp_label ? ast_channel_uniqueid(bridge_channel->chan) : NULL,
                                        stream, index)) {
-                       goto cleanup;
+                       SCOPE_EXIT_EXPR(goto cleanup, "%s: Couldn't append stream %d:%s\n",  ast_channel_name(bridge_channel->chan),
+                               index, ast_stream_get_name(stream));
                }
+               SCOPE_EXIT("%s:   Added new stream %s\n", ast_channel_name(bridge_channel->chan),
+                       ast_str_tmp(256, ast_stream_to_str(stream, &STR_TMP)));
        }
 
        /*  We always update the stored topology if we can to reflect what is currently negotiated */
@@ -2337,42 +2372,67 @@ static void softmix_bridge_stream_sources_update(struct ast_bridge *bridge, stru
         * other participants.
         */
        if (!removed_streams_count && !ast_stream_topology_get_count(added_streams)) {
+               ast_trace(-1, "%s: Nothing added or removed\n", ast_channel_name(bridge_channel->chan));
                goto cleanup;
        }
 
+       ast_trace(-1, "%s: Processing adds and removes\n", ast_channel_name(bridge_channel->chan));
        /* Go through each participant adding in the new streams and removing the old ones */
-       AST_LIST_TRAVERSE(participants, participant, entry) {
+       AST_LIST_TRAVERSE(participants, participant, entry)
+       {
+               struct softmix_channel *participant_sc = participant->tech_pvt;
+               SCOPE_ENTER(4, "%s/%s: Old participant topology %s\n",
+                       ast_channel_name(bridge_channel->chan),
+                       ast_channel_name(participant->chan),
+                       ast_str_tmp(256, ast_stream_topology_to_str(participant_sc->topology, &STR_TMP)));
+
                if (participant == bridge_channel) {
-                       continue;
+                       SCOPE_EXIT_EXPR(continue, "%s/%s: Same channel.  Skipping\n",
+                               ast_channel_name(bridge_channel->chan),
+                               ast_channel_name(participant->chan));
                }
 
-               sc = participant->tech_pvt;
-
                /* We add in all the new streams first so that they do not take the place
                 * of any of our removed streams, allowing the remote side to reset the state
                 * for each removed stream. */
-                if (append_all_streams(sc->topology, added_streams)) {
-                        goto cleanup;
-                }
+               if (append_all_streams(participant_sc->topology, added_streams)) {
+                       SCOPE_EXIT_EXPR(goto cleanup, "%s/%s: Couldn't append streams\n",  ast_channel_name(bridge_channel->chan),
+                               ast_channel_name(participant->chan));
+               }
+               ast_trace(-1, "%s/%s:   Adding streams %s\n", ast_channel_name(bridge_channel->chan),
+                       ast_channel_name(participant->chan),
+                       ast_str_tmp(256, ast_stream_topology_to_str(added_streams, &STR_TMP)));
 
                /* Then we go through and remove any ones that were removed */
-               for (index = 0; removed_streams_count && index < ast_stream_topology_get_count(sc->topology); ++index) {
+               for (index = 0;
+                       removed_streams_count && index < ast_stream_topology_get_count(sc->topology); ++index) {
                        struct ast_stream *stream = ast_stream_topology_get_stream(sc->topology, index);
                        int removed_stream;
 
                        for (removed_stream = 0; removed_stream < removed_streams_count; ++removed_stream) {
-                               if (is_video_dest(stream, ast_channel_name(bridge_channel->chan), removed_streams[removed_stream])) {
+                               if (is_video_dest(stream, ast_channel_name(bridge_channel->chan),
+                                       removed_streams[removed_stream])) {
+                                       ast_trace(-1, "%s/%s:    Removing stream %s\n",
+                                               ast_channel_name(bridge_channel->chan),
+                                               ast_channel_name(participant->chan),
+                                               ast_str_tmp(256, ast_stream_to_str(stream, &STR_TMP)));
                                        ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
                                }
                        }
                }
+               ast_channel_request_stream_topology_change(participant->chan, participant_sc->topology, NULL);
+               SCOPE_EXIT("%s/%s:   New participant topology %s\n",
+                       ast_channel_name(bridge_channel->chan),
+                       ast_channel_name(participant->chan),
+                       ast_str_tmp(256, ast_stream_topology_to_str(participant_sc->topology, &STR_TMP)));
+       }
 
-                ast_channel_request_stream_topology_change(participant->chan, sc->topology, NULL);
-        }
-
+       ast_trace(-1, "%s: New topology %s\n", ast_channel_name(bridge_channel->chan),
+               ast_str_tmp(256, ast_stream_topology_to_str(sc->topology, &STR_TMP)));
 
 cleanup:
        ast_stream_topology_free(added_streams);
+       SCOPE_EXIT();
 }
 
 /*!
@@ -2393,6 +2453,7 @@ static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, st
        struct ast_vector_int media_types;
        int nths[AST_MEDIA_TYPE_END] = {0};
        int idx;
+       SCOPE_ENTER(3, "%s: \n", ast_channel_name(bridge_channel->chan));
 
        switch (bridge->softmix.video_mode.mode) {
        case AST_BRIDGE_VIDEO_MODE_NONE:
@@ -2400,7 +2461,7 @@ static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, st
        case AST_BRIDGE_VIDEO_MODE_TALKER_SRC:
        default:
                ast_bridge_channel_stream_map(bridge_channel);
-               return;
+               SCOPE_EXIT_RTN("%s: Not in SFU mode\n", ast_channel_name(bridge_channel->chan));
        case AST_BRIDGE_VIDEO_MODE_SFU:
                break;
        }
@@ -2510,6 +2571,7 @@ static void softmix_bridge_stream_topology_changed(struct ast_bridge *bridge, st
        }
 
        AST_VECTOR_FREE(&media_types);
+       SCOPE_EXIT_RTN("%s\n", ast_channel_name(bridge_channel->chan));
 }
 
 static struct ast_bridge_technology softmix_bridge = {
index e99ec31..93889fc 100644 (file)
@@ -1585,8 +1585,11 @@ static struct topology_change_refresh_data *topology_change_refresh_data_alloc(
 
 static int on_topology_change_response(struct ast_sip_session *session, pjsip_rx_data *rdata)
 {
-       SCOPE_ENTER(1, "%s Status code: %d\n", ast_sip_session_get_name(session),
-               rdata->msg_info.msg->line.status.code);
+       SCOPE_ENTER(3, "%s: Received response code %d.  PT: %s  AT: %s\n", ast_sip_session_get_name(session),
+               rdata->msg_info.msg->line.status.code,
+               ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP)),
+               ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP)));
+
 
        if (PJSIP_IS_STATUS_IN_CLASS(rdata->msg_info.msg->line.status.code, 200)) {
                /* The topology was changed to something new so give notice to what requested
@@ -1594,27 +1597,33 @@ static int on_topology_change_response(struct ast_sip_session *session, pjsip_rx
                 */
                if (session->channel) {
                        ast_queue_control(session->channel, AST_CONTROL_STREAM_TOPOLOGY_CHANGED);
+                       SCOPE_EXIT_RTN_VALUE(0, "%s: Queued topology change frame\n", ast_sip_session_get_name(session));
                }
+               SCOPE_EXIT_RTN_VALUE(0, "%s: No channel?  Can't queue topology change frame\n", ast_sip_session_get_name(session));
        } else if (300 <= rdata->msg_info.msg->line.status.code) {
                /* The topology change failed, so drop the current pending media state */
                ast_sip_session_media_state_reset(session->pending_media_state);
+               SCOPE_EXIT_RTN_VALUE(0, "%s: response code > 300.  Resetting pending media state\n", ast_sip_session_get_name(session));
        }
 
-       SCOPE_EXIT_RTN_VALUE(0);
+       SCOPE_EXIT_RTN_VALUE(0, "%s: Nothing to do\n", ast_sip_session_get_name(session));
 }
 
 static int send_topology_change_refresh(void *data)
 {
        struct topology_change_refresh_data *refresh_data = data;
+       struct ast_sip_session *session = refresh_data->session;
        int ret;
-       SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(refresh_data->session));
+       SCOPE_ENTER(3, "%s: %s\n", ast_sip_session_get_name(session),
+               ast_str_tmp(256, ast_stream_topology_to_str(refresh_data->media_state->topology, &STR_TMP)));
 
-       ret = ast_sip_session_refresh(refresh_data->session, NULL, NULL, on_topology_change_response,
+
+       ret = ast_sip_session_refresh(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);
 
-       SCOPE_EXIT_RTN_VALUE(ret, "RC: %d\n", ret);
+       SCOPE_EXIT_RTN_VALUE(ret, "%s\n", ast_sip_session_get_name(session));
 }
 
 static int handle_topology_request_change(struct ast_sip_session *session,
@@ -1647,10 +1656,15 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
        size_t device_buf_size;
        int i;
        const struct ast_stream_topology *topology;
-       struct ast_frame f = { .frametype = AST_FRAME_CONTROL, .subclass = { .integer = condition } };
-       char subclass[40] = "";
-       SCOPE_ENTER(1, "%s Handling %s\n", ast_channel_name(ast),
-               ast_frame_subclass2str(&f, subclass, sizeof(subclass), NULL, 0));
+       struct ast_frame f = {
+               .frametype = AST_FRAME_CONTROL,
+               .subclass = {
+                       .integer = condition
+               }
+       };
+       char condition_name[256];
+       SCOPE_ENTER(3, "%s: Indicated %s\n", ast_channel_name(ast),
+               ast_frame_subclass2str(&f, condition_name, sizeof(condition_name), NULL, 0));
 
        switch (condition) {
        case AST_CONTROL_RINGING:
@@ -1755,9 +1769,9 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
                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);
-                       SCOPE_EXIT_RTN_VALUE(-1, "Couldn't increase the session reference counter\n");
+                       SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't increase the session reference counter\n",
+                               ast_channel_name(ast));
                }
 #endif
                if (ast_sip_push_task(channel->session->serializer, update_connected_line_information, channel->session)) {
@@ -1847,6 +1861,8 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
                break;
        case AST_CONTROL_STREAM_TOPOLOGY_REQUEST_CHANGE:
                topology = data;
+               ast_trace(-1, "%s: New topology: %s\n", ast_channel_name(ast),
+                       ast_str_tmp(256, ast_stream_topology_to_str(topology, &STR_TMP)));
                res = handle_topology_request_change(channel->session, topology);
                break;
        case AST_CONTROL_STREAM_TOPOLOGY_CHANGED:
@@ -1866,18 +1882,17 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
                struct indicate_data *ind_data = indicate_data_alloc(channel->session, condition, response_code, data, datalen);
 
                if (!ind_data) {
-                       SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create indicate data\n");
+                       SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't alloc indicate data\n", ast_channel_name(ast));
                }
 #ifdef HAVE_PJSIP_INV_SESSION_REF
                if (pjsip_inv_add_ref(ind_data->session->inv_session) != PJ_SUCCESS) {
-                       ast_log(LOG_ERROR, "Can't increase the session reference counter\n");
                        ao2_cleanup(ind_data);
-                       SCOPE_EXIT_RTN_VALUE(-1, "Couldn't increase the session reference counter\n");
+                       SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't increase the session reference counter\n", ast_channel_name(ast));
                }
 #endif
                if (ast_sip_push_task(channel->session->serializer, indicate, ind_data)) {
-                       ast_log(LOG_NOTICE, "Cannot send response code %d to endpoint %s. Could not queue task properly\n",
-                                       response_code, ast_sorcery_object_get_id(channel->session->endpoint));
+                       ast_log(LOG_ERROR, "%s: Cannot send response code %d to endpoint %s. Could not queue task properly\n",
+                                       ast_channel_name(ast), response_code, ast_sorcery_object_get_id(channel->session->endpoint));
 #ifdef HAVE_PJSIP_INV_SESSION_REF
                        pjsip_inv_dec_ref(ind_data->session->inv_session);
 #endif
@@ -1886,7 +1901,7 @@ static int chan_pjsip_indicate(struct ast_channel *ast, int condition, const voi
                }
        }
 
-       SCOPE_EXIT_RTN_VALUE(res, "RC: %d\n", res);
+       SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_channel_name(ast));
 }
 
 struct transfer_data {
@@ -3079,10 +3094,10 @@ static int chan_pjsip_incoming_request(struct ast_sip_session *session, struct p
        RAII_VAR(struct ast_datastore *, datastore, NULL, ao2_cleanup);
        struct transport_info_data *transport_data;
        pjsip_tx_data *packet = NULL;
-       SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
+       SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session));
 
        if (session->channel) {
-               SCOPE_EXIT_RTN_VALUE(0, "Already have channel\n");
+               SCOPE_EXIT_RTN_VALUE(0, "%s: No channel\n", ast_sip_session_get_name(session));
        }
 
        /* Check for a to-tag to determine if this is a reinvite */
@@ -3098,17 +3113,17 @@ static int chan_pjsip_incoming_request(struct ast_sip_session *session, struct p
                 */
                session->defer_terminate = 0;
                ast_sip_session_terminate(session, 400);
-               SCOPE_EXIT_RTN_VALUE(-1, "Reinvite without channel\n");
+               SCOPE_EXIT_RTN_VALUE(-1, "%s: We have a To tag but no channel.  Terminating session\n", ast_sip_session_get_name(session));
        }
 
        datastore = ast_sip_session_alloc_datastore(&transport_info, "transport_info");
        if (!datastore) {
-               SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create datastore\n");
+               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't alloc transport_info datastore\n", ast_sip_session_get_name(session));
        }
 
        transport_data = ast_calloc(1, sizeof(*transport_data));
        if (!transport_data) {
-               SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create transport_data\n");
+               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't alloc transport_info\n", ast_sip_session_get_name(session));
        }
        pj_sockaddr_cp(&transport_data->local_addr, &rdata->tp_info.transport->local_addr);
        pj_sockaddr_cp(&transport_data->remote_addr, &rdata->pkt_info.src_addr);
@@ -3121,12 +3136,12 @@ static int chan_pjsip_incoming_request(struct ast_sip_session *session, struct p
                        ast_sip_session_send_response(session, packet);
                }
 
-               ast_log(LOG_ERROR, "Failed to allocate new PJSIP channel on incoming SIP INVITE\n");
-               SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create channel\n");
+               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Failed to allocate new PJSIP channel on incoming SIP INVITE\n",
+                        ast_sip_session_get_name(session));
        }
        /* channel gets created on incoming request, but we wait to call start
            so other supplements have a chance to run */
-       SCOPE_EXIT_RTN_VALUE(0);
+       SCOPE_EXIT_RTN_VALUE(0, "%s\n", ast_sip_session_get_name(session));
 }
 
 static int call_pickup_incoming_request(struct ast_sip_session *session, pjsip_rx_data *rdata)
@@ -3225,11 +3240,10 @@ static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct
        struct pjsip_status_line status = rdata->msg_info.msg->line.status;
        struct ast_control_pvt_cause_code *cause_code;
        int data_size = sizeof(*cause_code);
-       SCOPE_ENTER(1, "%s Method: %.*s Status: %d\n", ast_sip_session_get_name(session),
-               (int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr, status.code);
+       SCOPE_ENTER(3, "%s: Status: %d\n", ast_sip_session_get_name(session), status.code);
 
        if (!session->channel) {
-               SCOPE_EXIT_RTN("No channel\n");
+               SCOPE_EXIT_RTN("%s: No channel\n", ast_sip_session_get_name(session));
        }
 
        /* Build and send the tech-specific cause information */
@@ -3249,8 +3263,7 @@ static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct
 
        switch (status.code) {
        case 180:
-               ast_trace(1, "%s Method: %.*s Status: %d  Queueing RINGING\n", ast_sip_session_get_name(session),
-                       (int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr, status.code);
+               ast_trace(-1, "%s: Queueing RINGING\n", ast_sip_session_get_name(session));
                ast_queue_control(session->channel, AST_CONTROL_RINGING);
                ast_channel_lock(session->channel);
                if (ast_channel_state(session->channel) != AST_STATE_UP) {
@@ -3259,6 +3272,7 @@ static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct
                ast_channel_unlock(session->channel);
                break;
        case 183:
+               ast_trace(-1, "%s: Queueing PROGRESS\n", ast_sip_session_get_name(session));
                if (session->endpoint->ignore_183_without_sdp) {
                        pjsip_rdata_sdp_info *sdp = pjsip_rdata_get_sdp_info(rdata);
                        if (sdp && sdp->body.ptr) {
@@ -3273,34 +3287,28 @@ static void chan_pjsip_incoming_response(struct ast_sip_session *session, struct
                }
                break;
        case 200:
-               ast_trace(1, "%s Method: %.*s Status: %d  Queueing ANSWER\n", ast_sip_session_get_name(session),
-                       (int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr, status.code);
-
+               ast_trace(-1, "%s: Queueing ANSWER\n", ast_sip_session_get_name(session));
                ast_queue_control(session->channel, AST_CONTROL_ANSWER);
                break;
        default:
-               ast_trace(1, "%s Method: %.*s Status: %d  Ignored\n", ast_sip_session_get_name(session),
-                       (int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr, status.code);
+               ast_trace(-1, "%s: Not queueing anything\n", ast_sip_session_get_name(session));
                break;
        }
 
-       SCOPE_EXIT_RTN();
+       SCOPE_EXIT_RTN("%s\n", ast_sip_session_get_name(session));
 }
 
 static int chan_pjsip_incoming_ack(struct ast_sip_session *session, struct pjsip_rx_data *rdata)
 {
-       SCOPE_ENTER(1, "%s Method: %.*s Status: %d  After Media\n", ast_sip_session_get_name(session),
-               (int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr,
-               rdata->msg_info.msg->line.status.code);
+       SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session));
 
        if (rdata->msg_info.msg->line.req.method.id == PJSIP_ACK_METHOD) {
                if (session->endpoint->media.direct_media.enabled && session->channel) {
-                       ast_trace(1, "%s Method: %.*s  Queueing SRCCHANGE\n", ast_sip_session_get_name(session),
-                               (int)rdata->msg_info.cseq->method.name.slen, rdata->msg_info.cseq->method.name.ptr);
+                       ast_trace(-1, "%s: Queueing SRCCHANGE\n", ast_sip_session_get_name(session));
                        ast_queue_control(session->channel, AST_CONTROL_SRCCHANGE);
                }
        }
-       SCOPE_EXIT_RTN_VALUE(0);
+       SCOPE_EXIT_RTN_VALUE(0, "%s\n", ast_sip_session_get_name(session));
 }
 
 static int update_devstate(void *obj, void *arg, int flags)
index 6872dc6..78c0bb3 100644 (file)
@@ -31903,6 +31903,7 @@ static struct sip_peer *build_peer(const char *name, struct ast_variable *v_head
                                        if ((!found && !ast_test_flag(&global_flags[1], SIP_PAGE2_RTCACHEFRIENDS)) || !peer->host_dynamic) {
                                                /* Initialize stuff if this is a new peer, or if it used to
                                                 * not be dynamic before the reload. */
+                                               ast_string_field_set(peer, tohost, NULL);
                                                ast_sockaddr_setnull(&peer->addr);
                                        }
                                        peer->host_dynamic = TRUE;
index 721f47f..176bc49 100644 (file)
@@ -1680,6 +1680,11 @@ int pjsip_acf_session_refresh_write(struct ast_channel *chan, const char *cmd, c
                return -1;
        }
 
+       if (ast_channel_state(chan) != AST_STATE_UP) {
+               ast_log(LOG_WARNING, "'%s' not allowed on unanswered channel '%s'.\n", cmd, ast_channel_name(chan));
+               return -1;
+       }
+
        if (strcmp(ast_channel_tech(chan)->type, "PJSIP")) {
                ast_log(LOG_WARNING, "Cannot call %s on a non-PJSIP channel\n", cmd);
                return -1;
index 9c7dcca..d917e63 100644 (file)
 ; levels, and is enclosed in square brackets. Valid formatters are:
 ;   - [default] - The default formatter, this outputs log messages using a
 ;                 human readable format.
+;   - [plain]   - The plain formatter, this outputs log messages using a
+;                 human readable format with the addition of function name
+;                 and line number. No color escape codes are ever printed
+                  nor are verbose messages treated specially.
 ;   - [json]    - Log the output in JSON. Note that JSON formatted log entries,
 ;                 if specified for a logger type of 'console', will be formatted
 ;                 per the 'default' formatter for log messages of type VERBOSE.
index 2636f63..506583e 100644 (file)
                            ; at the end of the joint list.
                            ; remote_first - Include only the first codec in
                            ; the remote list.
-;incoming_offer_codec_prefs=; This is a string that describes how the codecs
+;codec_prefs_incoming_offer=; This is a string that describes how the codecs
                             ; specified on an incoming SDP offer (pending) are
                             ; reconciled with the codecs specified on an endpoint
                             ; (configured) before being sent to the Asterisk core.
                             ;    | only_nonpreferred>,
                             ; keep: <first | all>,
                             ; transcode: <allow | prevent>
-;outgoing_offer_codec_prefs=; This is a string that describes how the codecs
+;codec_prefs_outgoing_offer=; This is a string that describes how the codecs
                             ; specified in the topology that comes from the
                             ; Asterisk core (pending) are reconciled with the
                             ; codecs specified on an endpoint (configured)
                             ;    | only_preferred | only_nonpreferred>,
                             ; keep: <first | all>,
                             ; transcode: <allow | prevent>
-;incoming_answer_codec_prefs=; This is a string that describes how the codecs
+;codec_prefs_incoming_answer=; This is a string that describes how the codecs
                              ; specified in an incoming SDP answer (pending)
                              ; are reconciled with the codecs specified on an
                              ; endpoint (configured) when receiving an SDP
                              ; operation: <intersect | union
                              ;    | only_preferred | only_nonpreferred>,
                              ; keep: <first | all>
-;outgoing_answer_codec_prefs=; This is a string that describes how the codecs
+;codec_prefs_outgoing_answer=; This is a string that describes how the codecs
                              ; that come from the core (pending) are reconciled
                              ; with the codecs specified on an endpoint
                              ; (configured) when sending an SDP answer.
                         ; refreshing.
                         ; (default: "no")
 ;type=  ; Must be of type aor (default: "")
-;qualify_frequency=0    ; Interval at which to qualify an AoR (default: "0")
+;qualify_frequency=0    ; Interval at which to qualify an AoR via OPTIONS requests.
+                        ; (default: "0")
 ;qualify_timeout=3.0      ; Qualify timeout in fractional seconds (default: "3.0")
 ;authenticate_qualify=no        ; Authenticates a qualify request if needed
                                 ; (default: "no")
                                                         ; int")
 ;debug=no ; Enable/Disable SIP debug logging.  Valid options include yes|no
           ; or a host address (default: "no")
-;keep_alive_interval=20 ; The interval (in seconds) at which to send keepalive
-                        ; messages on all active connection-oriented transports
-                        ; (default: "0")
+;keep_alive_interval=90 ; The interval (in seconds) at which to send (double CRLF)
+                        ; keep-alives on all active connection-oriented transports;
+                        ; for connection-less like UDP see qualify_frequency.
+                        ; (default: "90")
 ;contact_expiration_check_interval=30
                         ; The interval (in seconds) to check for expired contacts.
 ;disable_multi_domain=no
diff --git a/contrib/ast-db-manage/config/versions/1ae0609b6646_increse_reg_server_size.py b/contrib/ast-db-manage/config/versions/1ae0609b6646_increse_reg_server_size.py
new file mode 100644 (file)
index 0000000..afd234b
--- /dev/null
@@ -0,0 +1,22 @@
+"""increse reg server size
+
+Revision ID: 1ae0609b6646
+Revises: 61797b9fced6
+Create Date: 2020-08-31 13:50:19.772439
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = '1ae0609b6646'
+down_revision = '61797b9fced6'
+
+from alembic import op
+import sqlalchemy as sa
+
+
+def upgrade():
+    op.alter_column('ps_contacts', 'reg_server', type_=sa.String(255))
+
+
+def downgrade():
+    op.alter_column('ps_contacts', 'reg_server', type_=sa.String(20))
index 241185a..5e01204 100644 (file)
@@ -16,14 +16,14 @@ import sqlalchemy as sa
 max_value_length = 128
 
 def upgrade():
-    op.add_column('ps_endpoints', sa.Column('incoming_offer_codec_prefs', sa.String(max_value_length)))
-    op.add_column('ps_endpoints', sa.Column('outgoing_offer_codec_prefs', sa.String(max_value_length)))
-    op.add_column('ps_endpoints', sa.Column('incoming_answer_codec_prefs', sa.String(max_value_length)))
-    op.add_column('ps_endpoints', sa.Column('outgoing_answer_codec_prefs', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('codec_prefs_incoming_offer', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('codec_prefs_outgoing_offer', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('codec_prefs_incoming_answer', sa.String(max_value_length)))
+    op.add_column('ps_endpoints', sa.Column('codec_prefs_outgoing_answer', sa.String(max_value_length)))
 
 
 def downgrade():
-    op.drop_column('ps_endpoints', 'incoming_offer_codecs_prefs')
-    op.drop_column('ps_endpoints', 'outgoing_offer_codecs_prefs')
-    op.drop_column('ps_endpoints', 'incoming_answer_codecs_prefs')
-    op.drop_column('ps_endpoints', 'outgoing_answer_codecs_prefs')
+    op.drop_column('ps_endpoints', 'codec_prefs_incoming_offer')
+    op.drop_column('ps_endpoints', 'codec_prefs_outgoing_offer')
+    op.drop_column('ps_endpoints', 'codec_prefs_incoming_answer')
+    op.drop_column('ps_endpoints', 'codec_prefs_outgoing_answer')
diff --git a/contrib/ast-db-manage/config/versions/e658c26033ca_create_history_info_flag.py b/contrib/ast-db-manage/config/versions/e658c26033ca_create_history_info_flag.py
new file mode 100644 (file)
index 0000000..fa3d492
--- /dev/null
@@ -0,0 +1,38 @@
+"""create history info flag
+
+Revision ID: e658c26033ca
+Revises: 1ae0609b6646
+Create Date: 2020-08-13 10:53:21.032591
+
+"""
+
+# revision identifiers, used by Alembic.
+revision = 'e658c26033ca'
+down_revision = '1ae0609b6646'
+
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects.postgresql import ENUM
+
+AST_BOOL_NAME = 'ast_bool_values'
+# We'll just ignore the n/y and f/t abbreviations as Asterisk does not write
+# those aliases.
+AST_BOOL_VALUES = [ '0', '1',
+                    'off', 'on',
+                    'false', 'true',
+                    'no', 'yes' ]
+
+def upgrade():
+    ############################# Enums ##############################
+
+    # ast_bool_values has already been created, so use postgres enum object
+    # type to get around "already created" issue - works okay with mysql
+    ast_bool_values = ENUM(*AST_BOOL_VALUES, name=AST_BOOL_NAME, create_type=False)
+
+    op.add_column('ps_endpoints', sa.Column('send_history_info', ast_bool_values))
+
+
+def downgrade():
+    if op.get_context().bind.dialect.name == 'mssql':
+        op.drop_constraint('ck_ps_endpoints_send_history_info_ast_bool_values','ps_endpoints')
+    op.drop_column('ps_endpoints', 'send_history_info')
index ee62ab8..9d9f8bc 100755 (executable)
@@ -478,7 +478,7 @@ tail -n +${ss} $0 >$gdbinit
 
 # Now iterate over the coredumps and dump the debugging info
 for i in ${!COREDUMPS[@]} ; do
-       cf=${COREDUMPS[$i]}
+       cf=$(readlink -ne ${COREDUMPS[$i]})
        echo "Processing $cf"
 
        cfdir=`dirname ${cf}`
@@ -520,7 +520,7 @@ for i in ${!COREDUMPS[@]} ; do
                cp -a /${libdir}/asterisk/* ${dest}/${libdir}/asterisk/
                cp -a /usr/sbin/asterisk ${dest}/usr/sbin
                rm -rf ${tf}
-               tar -chzf ${tf} --transform="s/^[.]/${cfname}/" -C ${dest} .
+               tar -chzf ${tf} --transform="s/^[.]/${cfname}.output/" -C ${dest} .
                sleep 3
                rm -rf ${dest}
                echo "Created $tf"
index 2a4fc07..444fb67 100755 (executable)
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-WGET=`which wget`
-FETCH=`which fetch`
-if [ -x ${WGET} ]; then
-       externip=`${WGET} -q -O- http://www.whatismyip.org`
-elif [ -x ${FETCH} ]; then
-       externip=`${FETCH} -q -o - http://www.whatismyip.org`
-else
-       echo "no binary found to contact http://www.whatismyip.org"
-       exit 1
-fi
+# see http://unix.stackexchange.com/q/22615
+externip=`dig @resolver1.opendns.com -4 myip.opendns.com A +short`
 
 # optional parameter: network interface to use. By default: none.
 IFACE="$1"
@@ -49,8 +41,11 @@ OS=`uname -s`
 case "$OS" in
 Linux)
   echo "externip = $externip"
-  /sbin/ifconfig $IFACE | grep 'inet addr:' | grep Bcast \
-  | sed -e 's/^.*Bcast:\([0-9.]*\)\s*Mask:\([0-9.]*\)\s*$/localnet = \1\/\2/'
+  if [ -x "${IFACE}" ]; then
+         ip --brief -family inet address show scope global up dev $IFACE | awk '{print "localnet = " $3}'
+  else
+         ip --brief -family inet address show scope global up | awk '{print "localnet = " $3}'
+  fi
   ;;
 OpenBSD|FreeBSD)
   if [ "${OS}" = "FreeBSD" ]; then
diff --git a/doc/CHANGES-staging/logger_format.txt b/doc/CHANGES-staging/logger_format.txt
new file mode 100644 (file)
index 0000000..58d864d
--- /dev/null
@@ -0,0 +1,12 @@
+Subject: logger
+
+Added a new log formatter called "plain" that always prints
+file, function and line number if available (even for verbose
+messages) and never prints color control characters.  Most
+suitable for file output but can be used for other channels
+as well.
+
+You use it in logger.conf like so:
+debug => [plain]debug
+console => [plain]error,warning,debug,notice,pjsip_history
+messages => [plain]warning,error,verbose
diff --git a/doc/CHANGES-staging/pjsip_send_session_refresh.txt b/doc/CHANGES-staging/pjsip_send_session_refresh.txt
new file mode 100644 (file)
index 0000000..0705c29
--- /dev/null
@@ -0,0 +1,4 @@
+Subject: chan_pjsip
+
+The PJSIP_SEND_SESSION_REFRESH dialplan function now issues a warning, and
+returns unsuccessful if it's used on a channel prior to answering.
index 4504d6b..fb2fd4f 100644 (file)
@@ -194,6 +194,20 @@ struct ast_bridge_channel {
 };
 
 /*!
+ * \brief Get a ref to the bridge_channel's ast_channel
+ *
+ * \param bridge_channel The bridge channel
+ *
+ * \note The returned channel NEEDS to be unref'd once you are done with it. In general, this
+ * function is best used when accessing the bridge_channel chan from outside of a bridging
+ * thread.
+ *
+ * \retval ref'd ast_channel on success
+ * \retval NULL otherwise
+ */
+struct ast_channel *ast_bridge_channel_get_chan(struct ast_bridge_channel *bridge_channel);
+
+/*!
  * \brief Try locking the bridge_channel.
  *
  * \param bridge_channel What to try locking
index cc90c83..baefedd 100644 (file)
@@ -708,6 +708,19 @@ struct ast_channel_tech {
        int (* const answer)(struct ast_channel *chan);
 
        /*!
+        * \brief Answer the channel with topology
+        * \since 18.0.0
+        *
+        * \param chan The channel to answer
+        * \param topology The topology to use, probably the peer's.
+        *
+        * \note The topology may be NULL when the peer doesn't support streams
+        * or, in the case where transcoding is in effect, when this channel should use
+        * its existing topology.
+        */
+       int (* const answer_with_stream_topology)(struct ast_channel *chan, struct ast_stream_topology *topology);
+
+       /*!
         * \brief Read a frame (or chain of frames from the same stream), in standard format (see frame.h)
         *
         * \param chan channel to read frames from
@@ -1081,6 +1094,10 @@ struct ast_bridge_config {
         * exist when the end_bridge_callback is called, then it needs to be fixed up properly
         */
        void (*end_bridge_callback_data_fixup)(struct ast_bridge_config *bconfig, struct ast_channel *originator, struct ast_channel *terminator);
+       /*! If the bridge answers the channel this topology should be passed to the channel
+        * and used if the channel supports the answer_with_stream_topology callback.
+        */
+       struct ast_stream_topology *answer_topology;
 };
 
 struct chanmon;
@@ -1379,6 +1396,17 @@ int ast_queue_control_data(struct ast_channel *chan, enum ast_control_frame_type
                           const void *data, size_t datalen);
 
 /*!
+ * \brief Queue an ANSWER control frame with topology
+ *
+ * \param chan channel to queue frame onto
+ * \param topology topology to be passed through the core to the peer channel
+ *
+ * \retval 0 success
+ * \retval non-zero failure
+ */
+int ast_queue_answer(struct ast_channel *chan, const struct ast_stream_topology *topology);
+
+/*!
  * \brief Change channel name
  *
  * \pre Absolutely all channels _MUST_ be unlocked before calling this function.
@@ -1802,6 +1830,31 @@ int ast_auto_answer(struct ast_channel *chan);
 int ast_raw_answer(struct ast_channel *chan);
 
 /*!
+ * \brief Answer a channel passing in a stream topology
+ * \since 18.0.0
+ *
+ * \param chan channel to answer
+ * \param topology the peer's stream topology
+ *
+ * This function answers a channel and handles all necessary call
+ * setup functions.
+ *
+ * \note The channel passed does not need to be locked, but is locked
+ * by the function when needed.
+ *
+ * \note Unlike ast_answer(), this function will not wait for media
+ * flow to begin. The caller should be careful before sending media
+ * to the channel before incoming media arrives, as the outgoing
+ * media may be lost.
+ *
+ * \note The topology is usually that of the peer channel and may be NULL.
+ *
+ * \retval 0 on success
+ * \retval non-zero on failure
+ */
+int ast_raw_answer_with_stream_topology(struct ast_channel *chan, struct ast_stream_topology *topology);
+
+/*!
  * \brief Answer a channel, with a selectable delay before returning
  *
  * \param chan channel to answer
@@ -5054,4 +5107,18 @@ int ast_channel_stream_topology_changed_externally(struct ast_channel *chan);
  */
 void *ast_channel_get_stream_topology_change_source(struct ast_channel *chan);
 
+/*!
+ * \brief Checks if a channel's technology implements a particular callback function
+ * \since 18.0.0
+ *
+ * \param chan The channel
+ * \param function The function to look for
+ *
+ * \retval 1 if the channel has a technology set and it implements the function
+ * \retval 0 if the channel doesn't have a technology set or it doesn't implement the function
+ */
+#define ast_channel_has_tech_function(chan, function) \
+       (ast_channel_tech(chan) ? ast_channel_tech(chan)->function != NULL : 0)
+
+
 #endif /* _ASTERISK_CHANNEL_H */
index 55906e6..852d80f 100644 (file)
 #include <stdint.h>
 
 /*!
+ * \brief Convert the given string to a signed integer
+ *
+ * This function will return failure for the following reasons:
+ *
+ *   The given string to convert is NULL
+ *   The given string to convert is empty.
+ *   The given string to convert contains non numeric values
+ *   Once converted the number is out of range (less than INT_MIN
+ *       or greater than INT_MAX)
+ *
+ * \param str The string to convert
+ * \param res [out] The converted value
+ *
+ * \returns -1 if it fails to convert, 0 on success
+ */
+int ast_str_to_int(const char *str, int *res);
+
+/*!
  * \brief Convert the given string to an unsigned integer
  *
  * This function will return failure for the following reasons:
 int ast_str_to_uint(const char *str, unsigned int *res);
 
 /*!
+ * \brief Convert the given string to a signed long
+ *
+ * This function will return failure for the following reasons:
+ *
+ *   The given string to convert is NULL
+ *   The given string to convert is empty.
+ *   The given string to convert contains non numeric values
+ *   Once converted the number is out of range (less than LONG_MIN
+ *       or greater than LONG_MAX)
+ *
+ * \param str The string to convert
+ * \param res [out] The converted value
+ *
+ * \returns -1 if it fails to convert, 0 on success
+ */
+int ast_str_to_long(const char *str, long *res);
+
+/*!
  * \brief Convert the given string to an unsigned long
  *
  * This function will return failure for the following reasons:
@@ -62,6 +98,24 @@ int ast_str_to_uint(const char *str, unsigned int *res);
 int ast_str_to_ulong(const char *str, unsigned long *res);
 
 /*!
+ * \brief Convert the given string to a signed max size integer
+ *
+ * This function will return failure for the following reasons:
+ *
+ *   The given string to convert is NULL
+ *   The given string to convert is empty.
+ *   The given string to convert contains non numeric values
+ *   Once converted the number is out of range (less than INTMAX_MIN
+ *       or greater than INTMAX_MAX)
+ *
+ * \param str The string to convert
+ * \param res [out] The converted value
+ *
+ * \returns -1 if it fails to convert, 0 on success
+ */
+int ast_str_to_imax(const char *str, intmax_t *res);
+
+/*!
  * \brief Convert the given string to an unsigned max size integer
  *
  * This function will return failure for the following reasons:
index 5a2add6..7dc0276 100644 (file)
@@ -314,4 +314,17 @@ struct ast_format *ast_format_cache_get_slin_by_rate(unsigned int rate);
  */
 int ast_format_cache_is_slinear(struct ast_format *format);
 
+/*!
+ * \brief Retrieve a format from the cache by its codec
+ *
+ * \param codec The codec to search by
+ *
+ * \retval non-NULL if found
+ * \retval NULL if not found
+ *
+ * \note The returned format has its reference count incremented. It must be
+ * dropped using ao2_ref or ao2_cleanup.
+ */
+struct ast_format *ast_format_cache_get_by_codec(const struct ast_codec *codec);
+
 #endif /* _AST_FORMAT_CACHE_H */
index c8359ec..0e623d3 100644 (file)
@@ -107,7 +107,10 @@ enum ast_frame_type {
        AST_FRAME_NULL,
        /*! Inter Asterisk Exchange private frame type */
        AST_FRAME_IAX,
-       /*! Text messages */
+       /*! Text messages. The character data may not be zero-terminated, so
+        * care should be taken when passing it to functions that expect a
+        * zero-terminated string. The frame's datalen member should be used
+        * as it indicates the actual number of bytes available. */
        AST_FRAME_TEXT,
        /*! Image Frames */
        AST_FRAME_IMAGE,
@@ -147,8 +150,12 @@ enum {
 struct ast_frame_subclass {
        /*! A frame specific code */
        int integer;
-       /*! The asterisk media format */
-       struct ast_format *format;
+       union {
+               /*! The asterisk media format */
+               struct ast_format *format;
+               /*! The asterisk stream topology */
+               struct ast_stream_topology *topology;
+       };
        /*! For video formats, an indication that a frame ended */
        unsigned int frame_ending;
 };
index 5514d83..72c938d 100644 (file)
@@ -662,6 +662,7 @@ void __attribute__((format (printf, 6, 7))) __ast_trace(const char *file, int li
  *
  */
 #define ast_trace_raw(level, indent_type, ...) \
+       ast_debug(level < 0 ? __scope_level : level, " " __VA_ARGS__); \
        if (TRACE_ATLEAST(level)) { \
                __ast_trace(__FILE__, __LINE__, __PRETTY_FUNCTION__, indent_type, 0, " " __VA_ARGS__); \
        }
@@ -676,6 +677,7 @@ void __attribute__((format (printf, 6, 7))) __ast_trace(const char *file, int li
  *  This will print the file, line and function at the current indent level
  */
 #define ast_trace(level, ...) \
+       ast_debug(level < 0 ? __scope_level : level, " " __VA_ARGS__); \
        if (TRACE_ATLEAST(level)) { \
                __ast_trace(__FILE__, __LINE__, __PRETTY_FUNCTION__, AST_TRACE_INDENT_SAME, 0, " " __VA_ARGS__); \
        }
@@ -765,6 +767,7 @@ unsigned long _ast_trace_dec_indent(void);
 #define SCOPE_ENTER(level, ...) \
        int __scope_level = level; \
        int __scope_task = 0; \
+       ast_debug(__scope_level, " " __VA_ARGS__); \
        if (TRACE_ATLEAST(level)) { \
                __ast_trace(__FILE__, __LINE__, __PRETTY_FUNCTION__, AST_TRACE_INDENT_INC_AFTER, 0, " " __VA_ARGS__); \
        } \
@@ -772,6 +775,7 @@ unsigned long _ast_trace_dec_indent(void);
 #define SCOPE_ENTER_TASK(level, indent, ...) \
        int __scope_level = level; \
        int __scope_task = 1; \
+       ast_debug(__scope_level, " " __VA_ARGS__); \
        if (TRACE_ATLEAST(level)) { \
                __ast_trace(__FILE__, __LINE__, __PRETTY_FUNCTION__, AST_TRACE_INDENT_PROVIDED, indent, " " __VA_ARGS__); \
        } \
@@ -786,6 +790,7 @@ unsigned long _ast_trace_dec_indent(void);
  * This macro can be used at the exit points of a statement block since it just prints the message.
  */
 #define SCOPE_EXIT(...) \
+       ast_debug(__scope_level, " " __VA_ARGS__); \
        if (TRACE_ATLEAST(__scope_level)) { \
                __ast_trace(__FILE__, __LINE__, __PRETTY_FUNCTION__, AST_TRACE_INDENT_DEC_BEFORE, 0, " " __VA_ARGS__); \
                if (__scope_task) { \
@@ -814,6 +819,7 @@ unsigned long _ast_trace_dec_indent(void);
  * }
  */
 #define SCOPE_EXIT_EXPR(__expr, ...) \
+       ast_debug(__scope_level, " " __VA_ARGS__); \
        if (TRACE_ATLEAST(__scope_level)) { \
                __ast_trace(__FILE__, __LINE__, __PRETTY_FUNCTION__, AST_TRACE_INDENT_DEC_BEFORE, 0, " " __VA_ARGS__); \
                if (__scope_task) { \
@@ -833,6 +839,7 @@ unsigned long _ast_trace_dec_indent(void);
  * needs to be returned.
  */
 #define SCOPE_EXIT_RTN(...) \
+       ast_debug(__scope_level, " " __VA_ARGS__); \
        if (TRACE_ATLEAST(__scope_level)) { \
                __ast_trace(__FILE__, __LINE__, __PRETTY_FUNCTION__, AST_TRACE_INDENT_DEC_BEFORE, 0, " " __VA_ARGS__); \
                if (__scope_task) { \
@@ -853,6 +860,7 @@ unsigned long _ast_trace_dec_indent(void);
  * needs to be returned.
  */
 #define SCOPE_EXIT_RTN_VALUE(__return_value, ...) \
+       ast_debug(__scope_level, " " __VA_ARGS__); \
        if (TRACE_ATLEAST(__scope_level)) { \
                __ast_trace(__FILE__, __LINE__, __PRETTY_FUNCTION__, AST_TRACE_INDENT_DEC_BEFORE, 0, " " __VA_ARGS__); \
                if (__scope_task) { \
@@ -861,21 +869,79 @@ unsigned long _ast_trace_dec_indent(void);
        } \
        return(__return_value)
 
-#else
-#define ast_trace_raw(__level, __indent_type, ...)
-#define ast_trace(__level, ...)
+#else  /* AST_DEVMODE */
+#define ast_trace_raw(level, indent_type, ...) \
+       ast_debug(level < 0 ? __scope_level : level, " " __VA_ARGS__)
+
+#define ast_trace(level, ...) \
+       ast_debug(level < 0 ? __scope_level : level, " " __VA_ARGS__)
+
 #define ast_trace_get_indent() (0)
 #define ast_trace_set_indent(indent)
 #define ast_trace_inc_indent()
 #define ast_trace_dec_indent()
 #define SCOPE_TRACE(__level, ...)
-#define SCOPE_ENTER(level, ...)
-#define SCOPE_ENTER_TASK(level, indent, ...)
-#define SCOPE_EXIT(...)
-#define SCOPE_EXIT_EXPR(__expr, ...) __expr
-#define SCOPE_EXIT_RTN(...) return
-#define SCOPE_EXIT_RTN_VALUE(__return_value, ...) return __return_value
-#endif
+
+#define SCOPE_ENTER(level, ...) \
+       int __scope_level = level; \
+       ast_debug(level, " " __VA_ARGS__)
+
+#define SCOPE_ENTER_TASK(level, indent, ...) \
+       int __scope_level = level; \
+       ast_debug(level, " " __VA_ARGS__)
+
+#define SCOPE_EXIT(...) \
+       ast_debug(__scope_level, " " __VA_ARGS__)
+
+#define SCOPE_EXIT_EXPR(__expr, ...) \
+       ast_debug(__scope_level, " " __VA_ARGS__); \
+       __expr
+
+#define SCOPE_EXIT_RTN(...) \
+       ast_debug(__scope_level, " " __VA_ARGS__); \
+       return
+
+#define SCOPE_EXIT_RTN_VALUE(__return_value, ...) \
+       ast_debug(__scope_level, " " __VA_ARGS__); \
+       return __return_value
+
+#endif /* AST_DEVMODE */
+
+/*!
+ * The following macros will print log messages before running
+ * the associated SCOPE_ macro.
+ */
+
+#define SCOPE_EXIT_LOG(__log_level, ...) \
+({ \
+       ast_log(__log_level, " " __VA_ARGS__); \
+       SCOPE_EXIT(" " __VA_ARGS__); \
+})
+
+#define SCOPE_EXIT_LOG_RTN(__log_level, ...) \
+({ \
+       ast_log(__log_level, " " __VA_ARGS__); \
+       SCOPE_EXIT_RTN(" " __VA_ARGS__); \
+})
+
+#define SCOPE_EXIT_LOG_RTN_VALUE(__value, __log_level, ...) \
+({ \
+       ast_log(__log_level, " " __VA_ARGS__); \
+       SCOPE_EXIT_RTN_VALUE(__value, " " __VA_ARGS__); \
+})
+
+#define SCOPE_EXIT_LOG_EXPR(__expr, __log_level, ...) \
+({ \
+       ast_log(__log_level, " " __VA_ARGS__); \
+       SCOPE_EXIT_EXPR(__expr, " " __VA_ARGS__); \
+})
+
+#define ast_trace_log(__level, __log_level, ...) \
+({ \
+       ast_log(__log_level, " " __VA_ARGS__); \
+       ast_trace(__level < 0 ? __scope_level : __level, " " __VA_ARGS__); \
+})
+
 
 #if defined(__cplusplus) || defined(c_plusplus)
 }
index eaa9b21..ddcf02f 100644 (file)
@@ -648,6 +648,8 @@ struct ast_sip_endpoint_id_configuration {
        unsigned int send_connected_line;
        /*! When performing connected line update, which method should be used */
        enum ast_sip_session_refresh_method refresh_method;
+       /*! Do we add History-Info headers to applicable outgoing requests/responses? */
+       unsigned int send_history_info;
 };
 
 /*!
@@ -804,13 +806,13 @@ struct ast_sip_endpoint_media_configuration {
        /*! Codec preference for an outgoing offer */
        struct ast_flags outgoing_call_offer_pref;
        /*! Codec negotiation prefs for incoming offers */
-       struct ast_stream_codec_negotiation_prefs incoming_offer_codec_prefs;
+       struct ast_stream_codec_negotiation_prefs codec_prefs_incoming_offer;
        /*! Codec negotiation prefs for outgoing offers */
-       struct ast_stream_codec_negotiation_prefs outgoing_offer_codec_prefs;
+       struct ast_stream_codec_negotiation_prefs codec_prefs_outgoing_offer;
        /*! Codec negotiation prefs for incoming answers */
-       struct ast_stream_codec_negotiation_prefs incoming_answer_codec_prefs;
+       struct ast_stream_codec_negotiation_prefs codec_prefs_incoming_answer;
        /*! Codec negotiation prefs for outgoing answers */
-       struct ast_stream_codec_negotiation_prefs outgoing_answer_codec_prefs;
+       struct ast_stream_codec_negotiation_prefs codec_prefs_outgoing_answer;
 };
 
 /*!
index 9db68a8..1e83696 100644 (file)
@@ -121,6 +121,10 @@ struct ast_sip_session_media {
        unsigned int changed;
        /*! \brief Remote media stream label */
        char *remote_mslabel;
+       /*! \brief Remote stream label */
+       char *remote_label;
+       /*! \brief Stream name */
+       char *stream_name;
 };
 
 /*!
index ca637be..387c5b3 100644 (file)
@@ -474,6 +474,19 @@ const struct ast_format_cap *ast_stream_get_formats(const struct ast_stream *str
 const char *ast_stream_to_str(const struct ast_stream *stream, struct ast_str **buf);
 
 /*!
+ * \brief Get a stack allocated string representing the stream for debugging/display purposes
+ *
+ * \param stream A stream
+ *
+ * \returns a stack allocated pointer to a string representing the stream.
+ *
+ * \warning No attempt should ever be made to free the returned
+ * char* as it is allocated from the stack.
+ *
+ */
+#define ast_stream_to_stra(__stream) ast_str_tmp(128, ast_stream_to_str(__stream, &STR_TMP))
+
+/*!
  * \brief Get the count of the current negotiated formats of a stream
  *
  * \param stream The media stream
@@ -705,6 +718,8 @@ void ast_stream_topology_free(struct ast_stream_topology *topology);
  * \returns the position of the stream in the topology (-1 on error)
  *
  * \since 15
+ *
+ * \note If the stream's name is empty, it'll be set to <stream_type>-<position>
  */
 int ast_stream_topology_append_stream(struct ast_stream_topology *topology,
        struct ast_stream *stream);
@@ -762,6 +777,8 @@ struct ast_stream *ast_stream_topology_get_stream(
  * the first unused position.  You can't set positions beyond that.
  *
  * \since 15
+ *
+ * \note If the stream's name is empty, it'll be set to <stream_type>-<position>
  */
 int ast_stream_topology_set_stream(struct ast_stream_topology *topology,
        unsigned int position, struct ast_stream *stream);
@@ -954,4 +971,17 @@ struct ast_stream_topology *ast_stream_topology_create_resolved(
        struct ast_stream_codec_negotiation_prefs *prefs,
        struct ast_str **error_message);
 
+/*!
+ * \brief Get a stack allocated string representing the topology for debugging/display purposes
+ *
+ * \param topology A topology
+ *
+ * \returns a stack allocated pointer to a string representing the topology.
+ *
+ * \warning No attempt should ever be made to free the returned
+ * char* as it is allocated from the stack.
+ *
+ */
+#define ast_stream_topology_to_stra(__topology) ast_str_tmp(256, ast_stream_topology_to_str(__topology, &STR_TMP))
+
 #endif /* _AST_STREAM_H */
index f6280eb..0ee11ee 100644 (file)
@@ -267,7 +267,7 @@ int ast_base64decode(unsigned char *dst, const char *src, int max);
  * \brief Same as ast_base64decode, but does the math for you and returns
  * a decoded string
  *
- * \note The returned string will need to be freed later
+ * \note The returned string will need to be freed later and IS NULL terminated
  *
  * \param src The source buffer
  *
index 8f5cf8c..00d701b 100644 (file)
@@ -637,24 +637,28 @@ int ast_vector_string_split(struct ast_vector_string *dest,
  * \return 0 on success.
  * \return Non-zero on failure.
  */
-#define AST_VECTOR_COMPACT(vec) ({ \
-       int res = 0;                                                            \
-       do {                                                                                                            \
-               if ((vec)->max > (vec)->current) {                                              \
-                       size_t new_max = (vec)->current;                                \
-                       typeof((vec)->elems) new_elems = ast_realloc(           \
-                               (vec)->elems,                                                                   \
-                               new_max * sizeof(*new_elems));                                  \
-                       if (new_elems || (vec)->current == 0) {                         \
-                               (vec)->elems = new_elems;                                               \
-                               (vec)->max = new_max;                                                   \
-                       } else {                                                                                        \
-                               res = -1;                                                                               \
-                               break;                                                                                  \
-                       }                                                                                                       \
-               }                                                                                                               \
-       } while(0);                                                                                                     \
-       res;                                                                                                            \
+#define AST_VECTOR_COMPACT(vec) ({                                     \
+       int res = 0;                                                    \
+       do {                                                            \
+               size_t new_max = (vec)->current;                        \
+               if (new_max == 0) {                                     \
+                       ast_free((vec)->elems);                         \
+                       (vec)->elems = NULL;                            \
+                       (vec)->max = 0;                                 \
+               } else if ((vec)->max > new_max) {                      \
+                       typeof((vec)->elems) new_elems = ast_realloc(   \
+                               (vec)->elems,                           \
+                               new_max * sizeof(*new_elems));          \
+                       if (new_elems) {                                \
+                               (vec)->elems = new_elems;               \
+                               (vec)->max = new_max;                   \
+                       } else {                                        \
+                               res = -1;                               \
+                               break;                                  \
+                       }                                               \
+               }                                                       \
+       } while(0);                                                     \
+       res;                                                            \
 })
 
 /*!
index add586c..d44f659 100644 (file)
@@ -1729,7 +1729,10 @@ int ast_bridge_join(struct ast_bridge *bridge,
        ast_channel_lock(chan);
        ast_channel_internal_bridge_channel_set(chan, NULL);
        ast_channel_unlock(chan);
+       /* Due to a race condition, we lock the bridge channel here for ast_bridge_channel_get_chan */
+       ao2_lock(bridge_channel);
        bridge_channel->chan = NULL;
+       ao2_unlock(bridge_channel);
        /* If bridge_channel->swap is not NULL then the join failed. */
        ao2_t_cleanup(bridge_channel->swap, "Bridge complete: join failed");
        bridge_channel->swap = NULL;
@@ -1798,7 +1801,10 @@ static void *bridge_channel_ind_thread(void *data)
        ast_channel_lock(chan);
        ast_channel_internal_bridge_channel_set(chan, NULL);
        ast_channel_unlock(chan);
+       /* Lock here for ast_bridge_channel_get_chan */
+       ao2_lock(bridge_channel);
        bridge_channel->chan = NULL;
+       ao2_unlock(bridge_channel);
        /* If bridge_channel->swap is not NULL then the join failed. */
        ao2_t_cleanup(bridge_channel->swap, "Bridge complete: Independent impart join failed");
        bridge_channel->swap = NULL;
@@ -1899,7 +1905,10 @@ static int bridge_impart_internal(struct ast_bridge *bridge,
                ast_channel_lock(chan);
                ast_channel_internal_bridge_channel_set(chan, NULL);
                ast_channel_unlock(chan);
+               /* Lock here for ast_bridge_channel_get_chan */
+               ao2_lock(bridge_channel);
                bridge_channel->chan = NULL;
+               ao2_unlock(bridge_channel);
                ao2_t_cleanup(bridge_channel->swap, "Bridge complete: Impart failed");
                bridge_channel->swap = NULL;
                ast_bridge_features_destroy(bridge_channel->features);
@@ -3809,13 +3818,15 @@ void ast_bridge_set_single_src_video_mode(struct ast_bridge *bridge, struct ast_
        ast_bridge_lock(bridge);
        cleanup_video_mode(bridge);
        bridge->softmix.video_mode.mode = AST_BRIDGE_VIDEO_MODE_SINGLE_SRC;
-       bridge->softmix.video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan);
-       ast_verb(5, "Video source in bridge '%s' (%s) is now '%s' (%s)\n",
-               bridge->name, bridge->uniqueid,
-               ast_channel_name(video_src_chan),
-               ast_channel_uniqueid(video_src_chan));
+       if (video_src_chan) {
+               bridge->softmix.video_mode.mode_data.single_src_data.chan_vsrc = ast_channel_ref(video_src_chan);
+               ast_verb(5, "Video source in bridge '%s' (%s) is now '%s' (%s)\n",
+                       bridge->name, bridge->uniqueid,
+                       ast_channel_name(video_src_chan),
+                       ast_channel_uniqueid(video_src_chan));
+               ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
+       }
        ast_bridge_publish_state(bridge);
-       ast_indicate(video_src_chan, AST_CONTROL_VIDUPDATE);
        ast_bridge_unlock(bridge);
 }
 
@@ -4752,14 +4763,22 @@ enum ast_transfer_result ast_bridge_transfer_attended(struct ast_channel *to_tra
 
        if (to_transferee_bridge_channel) {
                /* Take off hold if they are on hold. */
-               ast_bridge_channel_write_unhold(to_transferee_bridge_channel);
+               if (ast_bridge_channel_write_unhold(to_transferee_bridge_channel)) {
+                       ast_log(LOG_ERROR, "Transferee channel disappeared during transfer!\n");
+                       res = AST_BRIDGE_TRANSFER_FAIL;
+                       goto end;
+               }
        }
 
        if (to_target_bridge_channel) {
                const char *target_complete_sound;
 
                /* Take off hold if they are on hold. */
-               ast_bridge_channel_write_unhold(to_target_bridge_channel);
+               if (ast_bridge_channel_write_unhold(to_target_bridge_channel)) {
+                       ast_log(LOG_ERROR, "Target channel disappeared during transfer!\n");
+                       res = AST_BRIDGE_TRANSFER_FAIL;
+                       goto end;
+               }
 
                /* Is there a courtesy sound to play to the target? */
                ast_channel_lock(to_transfer_target);
index 251dea7..3c5e87b 100644 (file)
@@ -208,6 +208,17 @@ static void bridge_sync_signal(struct bridge_sync *sync_struct)
        ast_sem_post(&sync_struct->sem);
 }
 
+struct ast_channel *ast_bridge_channel_get_chan(struct ast_bridge_channel *bridge_channel)
+{
+       struct ast_channel *chan;
+
+       ao2_lock(bridge_channel);
+       chan = ao2_bump(bridge_channel->chan);
+       ao2_unlock(bridge_channel);
+
+       return chan;
+}
+
 void ast_bridge_channel_lock_bridge(struct ast_bridge_channel *bridge_channel)
 {
        struct ast_bridge *bridge;
@@ -1177,7 +1188,14 @@ int ast_bridge_channel_write_hold(struct ast_bridge_channel *bridge_channel, con
 
 int ast_bridge_channel_write_unhold(struct ast_bridge_channel *bridge_channel)
 {
-       ast_channel_publish_cached_blob(bridge_channel->chan, ast_channel_unhold_type(), NULL);
+       struct ast_channel *chan = ast_bridge_channel_get_chan(bridge_channel);
+
+       if (!chan) {
+               return -1;
+       }
+
+       ast_channel_publish_cached_blob(chan, ast_channel_unhold_type(), NULL);
+       ao2_ref(chan, -1);
 
        return ast_bridge_channel_write_control_data(bridge_channel, AST_CONTROL_UNHOLD, NULL, 0);
 }
@@ -2377,6 +2395,41 @@ static void bridge_channel_handle_control(struct ast_bridge_channel *bridge_chan
 
 /*!
  * \internal
+ * \brief Ensure text data is zero terminated before sending
+ *
+ * \param chan Channel to send text to
+ * \param f The frame containing the text data to send
+ *
+ * \return Nothing
+ */
+static void sendtext_safe(struct ast_channel *chan, const struct ast_frame *f)
+{
+       if (f->datalen) {
+               char *text = f->data.ptr;
+
+               if (text[f->datalen - 1]) {
+                       /* Not zero terminated, we need to allocate */
+                       text = ast_strndup(text, f->datalen);
+                       if (!text) {
+                               return;
+                       }
+               }
+
+               ast_sendtext(chan, text);
+
+               if (text != f->data.ptr) {
+                       /* Only free if we allocated */
+                       ast_free(text);
+               }
+       } else {
+               /* Special case if the frame length is zero (although I
+                * am not sure this is possible?) */
+               ast_sendtext(chan, "");
+       }
+}
+
+/*!
+ * \internal
  * \brief Handle bridge channel write frame to channel.
  * \since 12.0.0
  *
@@ -2449,7 +2502,7 @@ static void bridge_channel_handle_write(struct ast_bridge_channel *bridge_channe
        case AST_FRAME_TEXT:
                ast_debug(1, "Sending TEXT frame to '%s': %*.s\n",
                        ast_channel_name(bridge_channel->chan), fr->datalen, (char *)fr->data.ptr);
-               ast_sendtext(bridge_channel->chan, fr->data.ptr);
+               sendtext_safe(bridge_channel->chan, fr);
                break;
        case AST_FRAME_TEXT_DATA:
                msg = (struct ast_msg_data *)fr->data.ptr;
index 8dd008d..276c1bc 100644 (file)
@@ -1238,6 +1238,17 @@ int ast_queue_control_data(struct ast_channel *chan, enum ast_control_frame_type
        return ast_queue_frame(chan, &f);
 }
 
+/*! \brief Queue an ANSWER control frame with topology */
+int ast_queue_answer(struct ast_channel *chan, const struct ast_stream_topology *topology)
+{
+       struct ast_frame f = {
+               AST_FRAME_CONTROL,
+               .subclass.integer = AST_CONTROL_ANSWER,
+               .subclass.topology = (struct ast_stream_topology *)topology,
+       };
+       return ast_queue_frame(chan, &f);
+}
+
 /*! \brief Set defer DTMF flag on channel */
 int ast_channel_defer_dtmf(struct ast_channel *chan)
 {
@@ -2619,7 +2630,8 @@ static void set_channel_answer_time(struct ast_channel *chan)
        }
 }
 
-int ast_raw_answer(struct ast_channel *chan)
+
+int ast_raw_answer_with_stream_topology(struct ast_channel *chan, struct ast_stream_topology *topology)
 {
        int res = 0;
        SCOPE_TRACE(1, "%s\n", ast_channel_name(chan));
@@ -2650,7 +2662,10 @@ int ast_raw_answer(struct ast_channel *chan)
        case AST_STATE_RINGING:
        case AST_STATE_RING:
                ast_channel_lock(chan);
-               if (ast_channel_tech(chan)->answer) {
+               if (ast_channel_tech(chan)->answer_with_stream_topology) {
+                       res = ast_channel_tech(chan)->answer_with_stream_topology(chan, topology);
+
+               } else if (ast_channel_tech(chan)->answer) {
                        res = ast_channel_tech(chan)->answer(chan);
                }
                ast_setstate(chan, AST_STATE_UP);
@@ -2667,6 +2682,11 @@ int ast_raw_answer(struct ast_channel *chan)
        return res;
 }
 
+int ast_raw_answer(struct ast_channel *chan)
+{
+       return ast_raw_answer_with_stream_topology(chan, NULL);
+}
+
 int __ast_answer(struct ast_channel *chan, unsigned int delay)
 {
        int res = 0;
@@ -11009,8 +11029,10 @@ int ast_channel_request_stream_topology_change(struct ast_channel *chan,
        }
 
        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));
+               ast_debug(2, "%s: Topologies already match. Current: %s  Requested: %s\n",
+                               ast_channel_name(chan),
+                               ast_str_tmp(256, ast_stream_topology_to_str(ast_channel_get_stream_topology(chan), &STR_TMP)),
+                               ast_str_tmp(256, ast_stream_topology_to_str(topology, &STR_TMP)));
                ast_channel_unlock(chan);
                return 0;
        }
index 2fd3146..007e24d 100644 (file)
@@ -41,6 +41,18 @@ static int str_is_negative(const char **str)
        return **str == '-';
 }
 
+int ast_str_to_int(const char *str, int *res)
+{
+       intmax_t val;
+
+       if (ast_str_to_imax(str, &val) || val < INT_MIN || val > INT_MAX) {
+               return -1;
+       }
+
+       *res = val;
+       return 0;
+}
+
 int ast_str_to_uint(const char *str, unsigned int *res)
 {
        uintmax_t val;
@@ -53,6 +65,18 @@ int ast_str_to_uint(const char *str, unsigned int *res)
        return 0;
 }
 
+int ast_str_to_long(const char *str, long *res)
+{
+       intmax_t val;
+
+       if (ast_str_to_imax(str, &val) || val < LONG_MIN || val > LONG_MAX) {
+               return -1;
+       }
+
+       *res = val;
+       return 0;
+}
+
 int ast_str_to_ulong(const char *str, unsigned long *res)
 {
        uintmax_t val;
@@ -65,6 +89,33 @@ int ast_str_to_ulong(const char *str, unsigned long *res)
        return 0;
 }
 
+int ast_str_to_imax(const char *str, intmax_t *res)
+{
+       char *end;
+       intmax_t val;
+
+       if (!str) {
+               return -1;
+       }
+
+       errno = 0;
+       val = strtoimax(str, &end, 0);
+
+       /*
+        * If str equals end then no digits were found. If end is not pointing to
+        * a null character then the string contained some numbers that could be
+        * converted, but some characters that could not, which we'll consider
+        * invalid.
+        */
+       if (str == end || *end != '\0' || (errno == ERANGE &&
+                       (val == INTMAX_MIN || val == INTMAX_MAX))) {
+               return -1;
+       }
+
+       *res = val;
+       return 0;
+}
+
 int ast_str_to_umax(const char *str, uintmax_t *res)
 {
        char *end;
index 51cc3ed..9810866 100644 (file)
@@ -76,6 +76,7 @@
 #include "asterisk/stasis_channels.h"
 #include "asterisk/features_config.h"
 #include "asterisk/max_forwards.h"
+#include "asterisk/stream.h"
 
 /*** DOCUMENTATION
        <application name="Bridge" language="en_US">
@@ -558,12 +559,17 @@ static int pre_bridge_setup(struct ast_channel *chan, struct ast_channel *peer,
        set_config_flags(chan, config);
 
        /* Answer if need be */
+
+       res = 0;
+
        if (ast_channel_state(chan) != AST_STATE_UP) {
-               if (ast_raw_answer(chan)) {
+               res = ast_raw_answer_with_stream_topology(chan, config->answer_topology);
+               if (res != 0) {
                        return -1;
                }
        }
 
+
 #ifdef FOR_DEBUG
        /* show the two channels and cdrs involved in the bridge for debug & devel purposes */
        ast_channel_log("Pre-bridge CHAN Channel info", chan);
index fbe659f..3ce8ee0 100644 (file)
@@ -555,3 +555,24 @@ int ast_format_cache_is_slinear(struct ast_format *format)
 
        return 0;
 }
+
+struct ast_format *ast_format_cache_get_by_codec(const struct ast_codec *codec)
+{
+       struct ast_format *format;
+       struct ao2_iterator it;
+
+       for (it = ao2_iterator_init(formats, 0);
+                (format = ao2_iterator_next(&it));
+                ao2_ref(format, -1)) {
+               struct ast_codec *candidate = ast_format_get_codec(format);
+               if (codec == candidate) {
+                       ao2_cleanup(candidate);
+                       ao2_iterator_destroy(&it);
+                       return format;
+               }
+               ao2_cleanup(candidate);
+       }
+
+       ao2_iterator_destroy(&it);
+       return NULL;
+}
index a97fd6d..4dc8e49 100644 (file)
@@ -232,7 +232,7 @@ int ast_format_cap_append_by_type(struct ast_format_cap *cap, enum ast_media_typ
                        continue;
                }
 
-               format = ast_format_cache_get(codec->name);
+               format = ast_format_cache_get_by_codec(codec);
 
                if (format == ast_format_none) {
                        ao2_ref(format, -1);
index 4eeb3b6..3a5ee91 100644 (file)
@@ -138,6 +138,8 @@ static void __frame_free(struct ast_frame *fr, int cache)
                                || fr->frametype == AST_FRAME_VIDEO
                                || fr->frametype == AST_FRAME_IMAGE) {
                                ao2_cleanup(fr->subclass.format);
+                       } else if (fr->frametype == AST_FRAME_CONTROL && fr->subclass.integer == AST_CONTROL_ANSWER) {
+                               ao2_cleanup(fr->subclass.topology);
                        }
 
                        AST_LIST_INSERT_HEAD(&frames->list, fr, frame_list);
@@ -160,6 +162,8 @@ static void __frame_free(struct ast_frame *fr, int cache)
                        || fr->frametype == AST_FRAME_VIDEO
                        || fr->frametype == AST_FRAME_IMAGE) {
                        ao2_cleanup(fr->subclass.format);
+               } else if (fr->frametype == AST_FRAME_CONTROL && fr->subclass.integer == AST_CONTROL_ANSWER) {
+                       ao2_cleanup(fr->subclass.topology);
                }
 
                ast_free(fr);
@@ -218,6 +222,8 @@ struct ast_frame *__ast_frisolate(struct ast_frame *fr, const char *file, int li
                if ((fr->frametype == AST_FRAME_VOICE) || (fr->frametype == AST_FRAME_VIDEO) ||
                        (fr->frametype == AST_FRAME_IMAGE)) {
                        ao2_bump(out->subclass.format);
+               } else if (fr->frametype == AST_FRAME_VOICE && fr->subclass.integer == AST_CONTROL_ANSWER) {
+                       ao2_bump(out->subclass.topology);
                }
                out->datalen = fr->datalen;
                out->samples = fr->samples;
@@ -348,7 +354,10 @@ struct ast_frame *__ast_frdup(const struct ast_frame *f, const char *file, int l
        if ((f->frametype == AST_FRAME_VOICE) || (f->frametype == AST_FRAME_VIDEO) ||
                (f->frametype == AST_FRAME_IMAGE)) {
                ao2_bump(out->subclass.format);
+       } else if (f->frametype == AST_FRAME_CONTROL && f->subclass.integer == AST_CONTROL_ANSWER) {
+               ao2_bump(out->subclass.topology);
        }
+
        out->datalen = f->datalen;
        out->samples = f->samples;
        out->delivery = f->delivery;
index 8894b05..d7d1807 100644 (file)
@@ -423,6 +423,56 @@ static struct logformatter logformatter_default = {
        .format_log = format_log_default,
 };
 
+static int format_log_plain(struct logchannel *chan, struct logmsg *msg, char *buf, size_t size)
+{
+       char call_identifier_str[13];
+       char linestr[32];
+       int has_file = !ast_strlen_zero(msg->file);
+       int has_line = (msg->line > 0);
+       int has_func = !ast_strlen_zero(msg->function);
+
+       if (msg->callid) {
+               snprintf(call_identifier_str, sizeof(call_identifier_str), "[C-%08x]", msg->callid);
+       } else {
+               call_identifier_str[0] = '\0';
+       }
+
+       switch (chan->type) {
+       case LOGTYPE_SYSLOG:
+               snprintf(buf, size, "%s[%d]%s: %s:%d in %s: %s",
+                    levels[msg->level], msg->lwp, call_identifier_str, msg->file,
+                    msg->line, msg->function, msg->message);
+               term_strip(buf, buf, size);
+               break;
+       case LOGTYPE_FILE:
+       case LOGTYPE_CONSOLE:
+               /* Turn the numerical line number into a string */
+               snprintf(linestr, sizeof(linestr), "%d", msg->line);
+               /* Build string to print out */
+               snprintf(buf, size, "[%s] %s[%d]%s: %s%s%s%s%s%s%s",
+                       msg->date,
+                       msg->level_name,
+                       msg->lwp,
+                       call_identifier_str,
+                       has_file ? msg->file : "",
+                       has_file ? ":" : "",
+                       has_line ? linestr : "",
+                       has_line ? " " : "",
+                       has_func ? msg->function : "",
+                       has_func ? ": " : "",
+                       msg->message);
+               term_strip(buf, buf, size);
+               break;
+       }
+
+       return 0;
+}
+
+static struct logformatter logformatter_plain = {
+       .name = "plain",
+       .format_log = format_log_plain,
+};
+
 static void make_components(struct logchannel *chan)
 {
        char *w;
@@ -449,6 +499,8 @@ static void make_components(struct logchannel *chan)
                                memcpy(&chan->formatter, &logformatter_json, sizeof(chan->formatter));
                        } else if (!strcasecmp(formatter_name, "default")) {
                                memcpy(&chan->formatter, &logformatter_default, sizeof(chan->formatter));
+                       } else if (!strcasecmp(formatter_name, "plain")) {
+                               memcpy(&chan->formatter, &logformatter_plain, sizeof(chan->formatter));
                        } else {
                                fprintf(stderr, "Logger Warning: Unknown formatter definition %s for %s in logger.conf; using 'default'\n",
                                        formatter_name, chan->filename);
index bbc6df9..b520f5f 100644 (file)
@@ -3148,10 +3148,15 @@ static int internal_extension_state_extended(struct ast_channel *c, const char *
        }
 
        if (e->exten[0] == '_') {
-               /* Create this hint on-the-fly */
+               /* Create this hint on-the-fly, we explicitly lock hints here to ensure the
+                * same locking order as if this were done through configuration file - that is
+                * hints is locked first and then (if needed) contexts is locked
+                */
+               ao2_lock(hints);
                ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
                        e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
                        e->registrar);
+               ao2_unlock(hints);
                if (!(e = ast_hint_extension(c, context, exten))) {
                        /* Improbable, but not impossible */
                        return -1;
@@ -3228,9 +3233,11 @@ int ast_hint_presence_state(struct ast_channel *c, const char *context, const ch
 
        if (e->exten[0] == '_') {
                /* Create this hint on-the-fly */
+               ao2_lock(hints);
                ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
                        e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
                        e->registrar);
+               ao2_unlock(hints);
                if (!(e = ast_hint_extension(c, context, exten))) {
                        /* Improbable, but not impossible */
                        return -1;
@@ -3766,9 +3773,11 @@ static int extension_state_add_destroy(const char *context, const char *exten,
         * individual extension, because the pattern will no longer match first.
         */
        if (e->exten[0] == '_') {
+               ao2_lock(hints);
                ast_add_extension(e->parent->name, 0, exten, e->priority, e->label,
                        e->matchcid ? e->cidmatch : NULL, e->app, ast_strdup(e->data), ast_free_ptr,
                        e->registrar);
+               ao2_unlock(hints);
                e = ast_hint_extension(NULL, context, exten);
                if (!e || e->exten[0] == '_') {
                        return -1;
index a21177d..e464c5e 100644 (file)
@@ -228,10 +228,12 @@ const char *ast_stream_state_map[] = {
        [AST_STREAM_STATE_INACTIVE] = "inactive",
 };
 
+#define MIN_STREAM_NAME_LEN 16
+
 struct ast_stream *ast_stream_alloc(const char *name, enum ast_media_type type)
 {
        struct ast_stream *stream;
-       size_t name_len = MAX(strlen(S_OR(name, "")), 7); /* Ensure there is enough room for 'removed' */
+       size_t name_len = MAX(strlen(S_OR(name, "")), MIN_STREAM_NAME_LEN); /* Ensure there is enough room for 'removed' or a type-position */
 
        stream = ast_calloc(1, sizeof(*stream) + name_len + 1);
        if (!stream) {
@@ -263,7 +265,7 @@ struct ast_stream *ast_stream_clone(const struct ast_stream *stream, const char
        }
 
        stream_name = name ?: stream->name;
-       name_len = MAX(strlen(stream_name), 7); /* Ensure there is enough room for 'removed' */
+       name_len = MAX(strlen(S_OR(stream_name, "")), MIN_STREAM_NAME_LEN); /* Ensure there is enough room for 'removed' or a type-position */
        new_stream = ast_calloc(1, sizeof(*stream) + name_len + 1);
        if (!new_stream) {
                return NULL;
@@ -343,7 +345,8 @@ const char *ast_stream_to_str(const struct ast_stream *stream, struct ast_str **
                return ast_str_buffer(*buf);
        }
 
-       ast_str_append(buf, 0, "%s:%s:%s ",
+       ast_str_append(buf, 0, "%d:%s:%s:%s ",
+               stream->position,
                S_OR(stream->name, "noname"),
                ast_codec_media_type2str(stream->type),
                ast_stream_state_map[stream->state]);
@@ -380,18 +383,6 @@ void ast_stream_set_state(struct ast_stream *stream, enum ast_stream_state state
 
        stream->state = state;
 
-       /* When a stream is set to removed that means that any previous data for it
-        * is no longer valid. We therefore change its name to removed and remove
-        * any old metadata associated with it.
-        */
-       if (state == AST_STREAM_STATE_REMOVED) {
-               strcpy(stream->name, "removed");
-               ast_variables_destroy(stream->metadata);
-               stream->metadata = NULL;
-               if (stream->formats) {
-                       ast_format_cap_remove_by_type(stream->formats, AST_MEDIA_TYPE_UNKNOWN);
-               }
-       }
 }
 
 const char *ast_stream_state2str(enum ast_stream_state state)
@@ -541,6 +532,10 @@ struct ast_stream *ast_stream_create_resolved(struct ast_stream *pending_stream,
        struct ast_stream *joint_stream;
        enum ast_media_type media_type = pending_stream ? pending_stream->type : AST_MEDIA_TYPE_UNKNOWN;
        int res = 0;
+       SCOPE_ENTER(4, "Pending: %s  Validation: %s  Prefs: %s\n",
+               ast_str_tmp(128, ast_stream_to_str(pending_stream, &STR_TMP)),
+               ast_str_tmp(128, ast_stream_to_str(validation_stream, &STR_TMP)),
+               ast_str_tmp(128, ast_stream_codec_prefs_to_str(prefs, &STR_TMP)));
 
        if (!pending_stream || !validation_stream || !prefs || !joint_caps
                || media_type == AST_MEDIA_TYPE_UNKNOWN) {
@@ -548,7 +543,7 @@ struct ast_stream *ast_stream_create_resolved(struct ast_stream *pending_stream,
                        ast_str_append(error_message, 0, "Invalid arguments");
                }
                ao2_cleanup(joint_caps);
-               return NULL;
+               SCOPE_EXIT_RTN_VALUE(NULL, "Invalid arguments\n");
        }
 
        if (prefs->prefer == CODEC_NEGOTIATION_PREFER_PENDING) {
@@ -592,7 +587,7 @@ struct ast_stream *ast_stream_create_resolved(struct ast_stream *pending_stream,
                }
 
                ao2_cleanup(joint_caps);
-               return NULL;
+               SCOPE_EXIT_RTN_VALUE(NULL, "No common formats available\n");
        }
 
        if (!ast_format_cap_empty(joint_caps)) {
@@ -602,6 +597,16 @@ struct ast_stream *ast_stream_create_resolved(struct ast_stream *pending_stream,
                        ast_format_cap_append(joint_caps, single, 0);
                        ao2_ref(single, -1);
                }
+       } else {
+               if (error_message) {
+                       ast_str_append(error_message, 0, "No common formats available for media type '%s' ",
+                               ast_codec_media_type2str(pending_stream->type));
+                       ast_format_cap_append_names(preferred_caps, error_message);
+                       ast_str_append(error_message, 0, "<>");
+                       ast_format_cap_append_names(nonpreferred_caps, error_message);
+                       ast_str_append(error_message, 0, " with prefs: ");
+                       ast_stream_codec_prefs_to_str(prefs, error_message);
+               }
        }
 
        joint_stream = ast_stream_clone(pending_stream, NULL);
@@ -613,7 +618,7 @@ struct ast_stream *ast_stream_create_resolved(struct ast_stream *pending_stream,
        /* ref to joint_caps will be transferred to the stream */
        ast_stream_set_formats(joint_stream, joint_caps);
 
-       if (TRACE_ATLEAST(1)) {
+       if (TRACE_ATLEAST(3)) {
                struct ast_str *buf = ast_str_create((AST_FORMAT_CAP_NAMES_LEN * 3) + AST_STREAM_MAX_CODEC_PREFS_LENGTH);
                if (buf) {
                        ast_str_set(&buf, 0, "Resolved '%s' stream ", ast_codec_media_type2str(pending_stream->type));
@@ -630,7 +635,7 @@ struct ast_stream *ast_stream_create_resolved(struct ast_stream *pending_stream,
        }
 
        ao2_cleanup(joint_caps);
-       return joint_stream;
+       SCOPE_EXIT_RTN_VALUE(joint_stream, "Joint stream: %s\n", ast_str_tmp(128, ast_stream_to_str(joint_stream, &STR_TMP)));
 }
 
 static void stream_topology_destroy(void *data)
@@ -750,6 +755,10 @@ int ast_stream_topology_append_stream(struct ast_stream_topology *topology, stru
 
        stream->position = AST_VECTOR_SIZE(&topology->streams) - 1;
 
+       if (ast_strlen_zero(stream->name)) {
+               snprintf(stream->name, MIN_STREAM_NAME_LEN, "%s-%d", ast_codec_media_type2str(stream->type), stream->position);
+       }
+
        return AST_VECTOR_SIZE(&topology->streams) - 1;
 }
 
@@ -806,6 +815,10 @@ int ast_stream_topology_set_stream(struct ast_stream_topology *topology,
                return AST_VECTOR_APPEND(&topology->streams, stream);
        }
 
+       if (ast_strlen_zero(stream->name)) {
+               snprintf(stream->name, MIN_STREAM_NAME_LEN, "%s-%d", ast_codec_media_type2str(stream->type), stream->position);
+       }
+
        return AST_VECTOR_REPLACE(&topology->streams, position, stream);
 }
 
@@ -864,7 +877,7 @@ struct ast_stream_topology *ast_stream_topology_create_from_format_cap(
                        return NULL;
                }
 
-               stream = ast_stream_alloc(ast_codec_media_type2str(type), type);
+               stream = ast_stream_alloc(NULL, type);
                if (!stream) {
                        ao2_cleanup(new_cap);
                        ast_stream_topology_free(topology);
@@ -879,6 +892,8 @@ struct ast_stream_topology *ast_stream_topology_create_from_format_cap(
                        ast_stream_topology_free(topology);
                        return NULL;
                }
+
+               snprintf(stream->name, MIN_STREAM_NAME_LEN, "%s-%d", ast_codec_media_type2str(stream->type), stream->position);
        }
 
        return topology;
@@ -1040,7 +1055,10 @@ struct ast_stream_topology *ast_stream_topology_create_resolved(
                        ast_stream_set_state(joint_stream, AST_STREAM_STATE_REMOVED);
                } else {
                        joint_stream = ast_stream_create_resolved(pending_stream, configured_stream, prefs, error_message);
-                       if (ast_stream_get_format_count(joint_stream) == 0) {
+                       if (!joint_stream) {
+                               ao2_cleanup(joint_topology);
+                               return NULL;
+                       } else if (ast_stream_get_format_count(joint_stream) == 0) {
                                ast_stream_set_state(joint_stream, AST_STREAM_STATE_REMOVED);
                        }
                }
index 0b6c649..827ee2e 100644 (file)
@@ -331,12 +331,13 @@ char *ast_base64decode_string(const char *src)
        }
 
        decoded_len = (encoded_len / 4 * 3) - padding;
-       decoded_string = ast_calloc(1, decoded_len);
+       decoded_string = ast_malloc(decoded_len + 1);
        if (!decoded_string) {
                return NULL;
        }
 
        ast_base64decode(decoded_string, src, decoded_len);
+       decoded_string[decoded_len] = '\0';
 
        return (char *)decoded_string;
 }
index 83a3532..95f43d0 100644 (file)
@@ -52,7 +52,7 @@ struct ast_ari_bridges_list_args {
 void ast_ari_bridges_list(struct ast_variable *headers, struct ast_ari_bridges_list_args *args, struct ast_ari_response *response);
 /*! Argument struct for ast_ari_bridges_create() */
 struct ast_ari_bridges_create_args {
-       /*! Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu). */
+       /*! Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu, video_single). */
        const char *type;
        /*! Unique ID to give to the bridge being created. */
        const char *bridge_id;
@@ -82,7 +82,7 @@ int ast_ari_bridges_create_parse_body(
 void ast_ari_bridges_create(struct ast_variable *headers, struct ast_ari_bridges_create_args *args, struct ast_ari_response *response);
 /*! Argument struct for ast_ari_bridges_create_with_id() */
 struct ast_ari_bridges_create_with_id_args {
-       /*! Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu) to set. */
+       /*! Comma separated list of bridge type attributes (mixing, holding, dtmf_events, proxy_media, video_sfu, video_single) to set. */
        const char *type;
        /*! Unique ID to give to the bridge being created. */
        const char *bridge_id;
index 3b4e25b..5bcdb21 100644 (file)
@@ -212,6 +212,7 @@ static int create_parked_subscription_full(struct ast_channel *chan, const char
        subscription_data->hangup_after = hangup_after;
        subscription_data->parkee_uuid = subscription_data->parker_uuid + parker_uuid_size;
        ast_copy_string(subscription_data->parkee_uuid, parkee_uuid, parkee_uuid_size);
+       ast_copy_string(subscription_data->parker_uuid, parker_uuid, parker_uuid_size);
 
        if (!(parked_datastore->parked_subscription = stasis_subscribe_pool(ast_parking_topic(), parker_update_cb, subscription_data))) {
                return -1;
index 01a14b9..3749d7b 100644 (file)
@@ -171,8 +171,8 @@ struct mohclass {
        char announcement[256];
        char mode[80];
        char digit;
-       /*! A vector of filenames in "files" mode */
-       struct ast_vector_string files;
+       /*! An immutable vector of filenames in "files" mode */
+       struct ast_vector_string *files;
        unsigned int flags;
        /*! The format from the MOH source, not applicable to "files" mode */
        struct ast_format *format;
@@ -313,6 +313,7 @@ static void moh_files_release(struct ast_channel *chan, void *data)
 static int ast_moh_files_next(struct ast_channel *chan)
 {
        struct moh_files_state *state = ast_channel_music_state(chan);
+       struct ast_vector_string *files;
        int tries;
        size_t file_count;
 
@@ -332,16 +333,21 @@ static int ast_moh_files_next(struct ast_channel *chan)
                state->announcement = 0;
        }
 
-       file_count = AST_VECTOR_SIZE(&state->class->files);
+       ao2_lock(state->class);
+       files = ao2_bump(state->class->files);
+       ao2_unlock(state->class);
+
+       file_count = AST_VECTOR_SIZE(files);
        if (!file_count) {
                ast_log(LOG_WARNING, "No files available for class '%s'\n", state->class->name);
+               ao2_ref(files, -1);
                return -1;
        }
 
        if (state->pos == 0 && ast_strlen_zero(state->save_pos_filename)) {
                /* First time so lets play the file. */
                state->save_pos = -1;
-       } else if (state->save_pos >= 0 && state->save_pos < file_count && !strcmp(AST_VECTOR_GET(&state->class->files, state->save_pos), state->save_pos_filename)) {
+       } else if (state->save_pos >= 0 && state->save_pos < file_count && !strcmp(AST_VECTOR_GET(files, state->save_pos), state->save_pos_filename)) {
                /* If a specific file has been saved confirm it still exists and that it is still valid */
                state->pos = state->save_pos;
                state->save_pos = -1;
@@ -349,7 +355,7 @@ static int ast_moh_files_next(struct ast_channel *chan)
                /* Get a random file and ensure we can open it */
                for (tries = 0; tries < 20; tries++) {
                        state->pos = ast_random() % file_count;
-                       if (ast_fileexists(AST_VECTOR_GET(&state->class->files, state->pos), NULL, NULL) > 0) {
+                       if (ast_fileexists(AST_VECTOR_GET(files, state->pos), NULL, NULL) > 0) {
                                break;
                        }
                }
@@ -364,21 +370,22 @@ static int ast_moh_files_next(struct ast_channel *chan)
        }
 
        for (tries = 0; tries < file_count; ++tries) {
-               if (ast_openstream_full(chan, AST_VECTOR_GET(&state->class->files, state->pos), ast_channel_language(chan), 1)) {
+               if (ast_openstream_full(chan, AST_VECTOR_GET(files, state->pos), ast_channel_language(chan), 1)) {
                        break;
                }
 
-               ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", AST_VECTOR_GET(&state->class->files, state->pos), strerror(errno));
+               ast_log(LOG_WARNING, "Unable to open file '%s': %s\n", AST_VECTOR_GET(files, state->pos), strerror(errno));
                state->pos++;
                state->pos %= file_count;
        }
 
        if (tries == file_count) {
+               ao2_ref(files, -1);
                return -1;
        }
 
        /* Record the pointer to the filename for position resuming later */
-       ast_copy_string(state->save_pos_filename, AST_VECTOR_GET(&state->class->files, state->pos), sizeof(state->save_pos_filename));
+       ast_copy_string(state->save_pos_filename, AST_VECTOR_GET(files, state->pos), sizeof(state->save_pos_filename));
 
        ast_debug(1, "%s Opened file %d '%s'\n", ast_channel_name(chan), state->pos, state->save_pos_filename);
 
@@ -395,6 +402,7 @@ static int ast_moh_files_next(struct ast_channel *chan)
                }
        }
 
+       ao2_ref(files, -1);
        return 0;
 }
 
@@ -504,7 +512,9 @@ static void *moh_files_alloc(struct ast_channel *chan, void *params)
                }
        }
 
-       file_count = AST_VECTOR_SIZE(&class->files);
+       ao2_lock(class);
+       file_count = AST_VECTOR_SIZE(class->files);
+       ao2_unlock(class);
 
        /* Resume MOH from where we left off last time or start from scratch? */
        if (state->save_total != file_count || strcmp(state->name, class->name) != 0) {
@@ -1074,8 +1084,29 @@ static struct ast_generator mohgen = {
        .digit    = moh_handle_digit,
 };
 
+static void moh_file_vector_destructor(void *obj)
+{
+       struct ast_vector_string *files = obj;
+       AST_VECTOR_RESET(files, ast_free);
+       AST_VECTOR_FREE(files);
+}
+
+static struct ast_vector_string *moh_file_vector_alloc(int initial_capacity)
+{
+       struct ast_vector_string *files = ao2_alloc_options(
+               sizeof(struct ast_vector_string),
+               moh_file_vector_destructor,
+               AO2_ALLOC_OPT_LOCK_NOLOCK);
+       if (files) {
+               AST_VECTOR_INIT(files, initial_capacity);
+       }
+       return files;
+}
+
 static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclass)
 {
+       struct ast_vector_string *playlist_entries = NULL;
+
        for (; var; var = var->next) {
                if (!strcasecmp(var->name, "name")) {
                        ast_copy_string(mohclass->name, var->value, sizeof(mohclass->name));
@@ -1083,7 +1114,16 @@ static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclas
                        ast_copy_string(mohclass->mode, var->value, sizeof(mohclass->mode));
                } else if (!strcasecmp(var->name, "entry")) {
                        if (ast_begins_with(var->value, "/") || ast_begins_with(var->value, "http://") || ast_begins_with(var->value, "https://")) {
-                               char *dup = ast_strdup(var->value);
+                               char *dup;
+
+                               if (!playlist_entries) {
+                                       playlist_entries = moh_file_vector_alloc(16);
+                                       if (!playlist_entries) {
+                                               continue;
+                                       }
+                               }
+
+                               dup = ast_strdup(var->value);
                                if (!dup) {
                                        continue;
                                }
@@ -1096,7 +1136,8 @@ static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclas
                                                        dup);
                                        }
                                }
-                               AST_VECTOR_APPEND(&mohclass->files, dup);
+
+                               AST_VECTOR_APPEND(playlist_entries, dup);
                        } else {
                                ast_log(LOG_ERROR, "Playlist entries must be a URL or absolute path, '%s' provided.\n", var->value);
                        }
@@ -1150,86 +1191,120 @@ static void moh_parse_options(struct ast_variable *var, struct mohclass *mohclas
                }
        }
 
-       AST_VECTOR_COMPACT(&mohclass->files);
-}
+       if (playlist_entries) {
+               /* If we aren't in playlist mode, drop any list we may have already built */
+               if (strcasecmp(mohclass->mode, "playlist")) {
+                       ast_log(LOG_NOTICE, "Ignoring playlist entries because we are in '%s' mode.\n",
+                               mohclass->mode);
+                       ao2_ref(playlist_entries, -1);
+                       return;
+               }
 
-static int moh_scan_files(struct mohclass *class) {
+               AST_VECTOR_COMPACT(playlist_entries);
 
-       DIR *files_DIR;
-       struct dirent *files_dirent;
-       char dir_path[PATH_MAX - sizeof(class->dir)];
-       char filepath[PATH_MAX];
-       char *ext;
-       struct stat statbuf;
-       int res;
+               /* We don't need to lock here because we are the thread that
+                * created this mohclass and we haven't published it yet */
+               ao2_replace(mohclass->files, playlist_entries);
+       }
+}
 
-       if (class->dir[0] != '/') {
-               snprintf(dir_path, sizeof(dir_path), "%s/%s", ast_config_AST_DATA_DIR, class->dir);
-       } else {
-               ast_copy_string(dir_path, class->dir, sizeof(dir_path));
+static int on_moh_file(const char *directory, const char *filename, void *obj)
+{
+       struct ast_vector_string *files = obj;
+       char *full_path;
+       char *extension;
+
+       /* Skip files that starts with a dot */
+       if (*filename == '.') {
+               ast_debug(4, "Skipping '%s/%s' because it starts with a dot\n",
+                       directory, filename);
+               return 0;
        }
-       ast_debug(4, "Scanning '%s' for files for class '%s'\n", dir_path, class->name);
-       files_DIR = opendir(dir_path);
-       if (!files_DIR) {
-               ast_log(LOG_WARNING, "Cannot open dir %s or dir does not exist\n", dir_path);
-               return -1;
+
+       /* We can't do anything with files that don't have an extension,
+        * so check that first and punt if we can't find something */
+       extension = strrchr(filename, '.');
+       if (!extension) {
+               ast_debug(4, "Skipping '%s/%s' because it doesn't have an extension\n",
+                       directory, filename);
+               return 0;
        }
 
-       AST_VECTOR_RESET(&class->files, ast_free);
+       /* The extension needs at least two characters (after the .) to be useful */
+       if (strlen(extension) < 3) {
+               ast_debug(4, "Skipping '%s/%s' because it doesn't have at least a two "
+                       "character extension\n", directory, filename);
+               return 0;
+       }
 
-       while ((files_dirent = readdir(files_DIR))) {
-               char *filepath_copy;
+       /* Build the full path (excluding the extension) */
+       if (ast_asprintf(&full_path, "%s/%.*s",
+                       directory,
+                       (int) (extension - filename), filename) < 0) {
+               /* If we don't have enough memory to build this path, there is no
+                * point in continuing */
+               return 1;
+       }
 
-               /* The file name must be at least long enough to have the file type extension */
-               if ((strlen(files_dirent->d_name) < 4))
-                       continue;
+       /* If the file is present in multiple formats, ensure we only put it
+        * into the list once. Pretty sure this is O(n^2). */
+       if (AST_VECTOR_GET_CMP(files, &full_path[0], !strcmp)) {
+               ast_free(full_path);
+               return 0;
+       }
 
-               /* Skip files that starts with a dot */
-               if (files_dirent->d_name[0] == '.')
-                       continue;
+       if (AST_VECTOR_APPEND(files, full_path)) {
+               /* AST_VECTOR_APPEND() can only fail on allocation failure, so
+                * we stop iterating */
+               ast_free(full_path);
+               return 1;
+       }
 
-               /* Skip files without extensions... they are not audio */
-               if (!strchr(files_dirent->d_name, '.'))
-                       continue;
+       return 0;
+}
 
-               snprintf(filepath, sizeof(filepath), "%s/%s", dir_path, files_dirent->d_name);
+static int moh_filename_strcasecmp(const void *a, const void *b)
+{
+       const char **s1 = (const char **) a;
+       const char **s2 = (const char **) b;
+       return strcasecmp(*s1, *s2);
+}
 
-               if (stat(filepath, &statbuf))
-                       continue;
+static int moh_scan_files(struct mohclass *class) {
 
-               if (!S_ISREG(statbuf.st_mode))
-                       continue;
+       char dir_path[PATH_MAX - sizeof(class->dir)];
+       struct ast_vector_string *files;
 
-               if ((ext = strrchr(filepath, '.')))
-                       *ext = '\0';
+       if (class->dir[0] != '/') {
+               snprintf(dir_path, sizeof(dir_path), "%s/%s", ast_config_AST_DATA_DIR, class->dir);
+       } else {
+               ast_copy_string(dir_path, class->dir, sizeof(dir_path));
+       }
 
-               /* if the file is present in multiple formats, ensure we only put it into the list once */
-               if (AST_VECTOR_GET_CMP(&class->files, &filepath[0], !strcmp)) {
-                       continue;
-               }
+       ast_debug(4, "Scanning '%s' for files for class '%s'\n", dir_path, class->name);
 
-               filepath_copy = ast_strdup(filepath);
-               if (!filepath_copy) {
-                       break;
-               }
+       /* 16 seems like a reasonable default */
+       files = moh_file_vector_alloc(16);
+       if (!files) {
+               return -1;
+       }
 
-               if (ast_test_flag(class, MOH_SORTALPHA)) {
-                       res = AST_VECTOR_ADD_SORTED(&class->files, filepath_copy, strcasecmp);
-               } else {
-                       res = AST_VECTOR_APPEND(&class->files, filepath_copy);
-               }
+       if (ast_file_read_dir(dir_path, on_moh_file, files)) {
+               ao2_ref(files, -1);
+               return -1;
+       }
 
-               if (res) {
-                       ast_free(filepath_copy);
-                       break;
-               }
+       if (ast_test_flag(class, MOH_SORTALPHA)) {
+               AST_VECTOR_SORT(files, moh_filename_strcasecmp);
        }
 
-       closedir(files_DIR);
+       AST_VECTOR_COMPACT(files);
 
-       AST_VECTOR_COMPACT(&class->files);
+       ao2_lock(class);
+       ao2_replace(class->files, files);
+       ao2_unlock(class);
 
-       return AST_VECTOR_SIZE(&class->files);
+       return AST_VECTOR_SIZE(files);
 }
 
 static int init_files_class(struct mohclass *class)
@@ -1355,7 +1430,13 @@ static int _moh_register(struct mohclass *moh, int reload, int unref, const char
                        return -1;
                }
        } else if (!strcasecmp(moh->mode, "playlist")) {
-               if (!AST_VECTOR_SIZE(&moh->files)) {
+               size_t file_count;
+
+               ao2_lock(moh);
+               file_count = AST_VECTOR_SIZE(moh->files);
+               ao2_unlock(moh);
+
+               if (!file_count) {
                        if (unref) {
                                moh = mohclass_unref(moh, "unreffing potential new moh class (no playlist entries)");
                        }
@@ -1504,7 +1585,13 @@ static struct mohclass *_moh_class_malloc(const char *file, int line, const char
                class->format = ao2_bump(ast_format_slin);
                class->srcfd = -1;
                class->kill_delay = 100000;
-               AST_VECTOR_INIT(&class->files, 0);
+
+               /* We create an empty one by default */
+               class->files = moh_file_vector_alloc(0);
+               if (!class->files) {
+                       ao2_ref(class, -1);
+                       return NULL;
+               }
        }
 
        return class;
@@ -1647,6 +1734,14 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
                                mohclass->start -= respawn_time;
 
                                if (!strcasecmp(mohclass->mode, "files")) {
+                                       /*
+                                        * XXX moh_scan_files returns -1 if it is unable to open the
+                                        * configured directory or there is a memory allocation
+                                        * failure. Otherwise it returns the number of files for this music
+                                        * class. This check is only checking if the number of files is zero
+                                        * and it ignores the -1 case. To avoid a behavior change we keep this
+                                        * as-is, but we should address what the 'correct' behavior should be.
+                                        */
                                        if (!moh_scan_files(mohclass)) {
                                                mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (moh_scan_files failed)");
                                                return -1;
@@ -1660,7 +1755,13 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
                                                ast_set_flag(mohclass, MOH_RANDOMIZE);
                                        }
                                } else if (!strcasecmp(mohclass->mode, "playlist")) {
-                                       if (!AST_VECTOR_SIZE(&mohclass->files)) {
+                                       size_t file_count;
+
+                                       ao2_lock(mohclass);
+                                       file_count = AST_VECTOR_SIZE(mohclass->files);
+                                       ao2_unlock(mohclass);
+
+                                       if (!file_count) {
                                                mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (no playlist entries)");
                                                return -1;
                                        }
@@ -1723,6 +1824,13 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
 
        /* If we are using a cached realtime class with files, re-scan the files */
        if (!var && ast_test_flag(global_flags, MOH_CACHERTCLASSES) && mohclass->realtime && !strcasecmp(mohclass->mode, "files")) {
+               /*
+                * XXX moh_scan_files returns -1 if it is unable to open the configured directory
+                * or there is a memory allocation failure. Otherwise it returns the number of
+                * files for this music class. This check is only checking if the number of files
+                * is zero and it ignores the -1 case. To avoid a behavior change we keep this
+                * as-is, but we should address what the 'correct' behavior should be.
+                */
                if (!moh_scan_files(mohclass)) {
                        mohclass = mohclass_unref(mohclass, "unreffing potential mohclass (moh_scan_files failed)");
                        return -1;
@@ -1730,7 +1838,13 @@ static int local_ast_moh_start(struct ast_channel *chan, const char *mclass, con
        }
 
        if (!state || !state->class || strcmp(mohclass->name, state->class->name)) {
-               if (AST_VECTOR_SIZE(&mohclass->files)) {
+               size_t file_count;
+
+               ao2_lock(mohclass);
+               file_count = AST_VECTOR_SIZE(mohclass->files);
+               ao2_unlock(mohclass);
+
+               if (file_count) {
                        res = ast_activate_generator(chan, &moh_file_stream, mohclass);
                } else {
                        res = ast_activate_generator(chan, &mohgen, mohclass);
@@ -1775,6 +1889,7 @@ static void moh_class_destructor(void *obj)
        while ((member = AST_LIST_REMOVE_HEAD(&class->members, list))) {
                ast_free(member);
        }
+       ao2_cleanup(class->files);
        ao2_unlock(class);
 
        /* Kill the thread first, so it cannot restart the child process while the
@@ -1809,9 +1924,6 @@ static void moh_class_destructor(void *obj)
                class->srcfd = -1;
        }
 
-       AST_VECTOR_RESET(&class->files, ast_free);
-       AST_VECTOR_FREE(&class->files);
-
        if (class->timer) {
                ast_timer_close(class->timer);
                class->timer = NULL;
@@ -1993,16 +2105,21 @@ static char *handle_cli_moh_show_files(struct ast_cli_entry *e, int cmd, struct
 
        i = ao2_iterator_init(mohclasses, 0);
        for (; (class = ao2_t_iterator_next(&i, "Show files iterator")); mohclass_unref(class, "Unref iterator in moh show files")) {
-               int x;
+               struct ast_vector_string *files;
 
-               if (!AST_VECTOR_SIZE(&class->files)) {
-                       continue;
-               }
+               ao2_lock(class);
+               files = ao2_bump(class->files);
+               ao2_unlock(class);
 
-               ast_cli(a->fd, "Class: %s\n", class->name);
-               for (x = 0; x < AST_VECTOR_SIZE(&class->files); x++) {
-                       ast_cli(a->fd, "\tFile: %s\n", AST_VECTOR_GET(&class->files, x));
+               if (AST_VECTOR_SIZE(files)) {
+                       int x;
+                       ast_cli(a->fd, "Class: %s\n", class->name);
+                       for (x = 0; x < AST_VECTOR_SIZE(files); x++) {
+                               ast_cli(a->fd, "\tFile: %s\n", AST_VECTOR_GET(files, x));
+                       }
                }
+
+               ao2_ref(files, -1);
        }
        ao2_iterator_destroy(&i);
 
index bb77e54..9d3a6c2 100644 (file)
                                <configOption name="allow">
                                        <synopsis>Media Codec(s) to allow</synopsis>
                                </configOption>
-                               <configOption name="incoming_offer_codec_prefs">
+                               <configOption name="codec_prefs_incoming_offer">
                                        <synopsis>Codec negotiation prefs for incoming offers.</synopsis>
                                        <description>
                                                <para>
                                                <para>
                                                </para>
                                                <example>
-                                                       incoming_offer_codec_prefs = prefer: pending, operation: intersect, keep: all, transcode: allow
+                                                       codec_prefs_incoming_offer = prefer: pending, operation: intersect, keep: all, transcode: allow
                                                </example>
                                                <para>
                                                        Prefer the codecs coming from the caller.  Use only the ones that are common.
                                                </para>
                                        </description>
                                </configOption>
-                               <configOption name="outgoing_offer_codec_prefs">
+                               <configOption name="codec_prefs_outgoing_offer">
                                        <synopsis>Codec negotiation prefs for outgoing offers.</synopsis>
                                        <description>
                                                <para>
                                                <para>
                                                </para>
                                                <example>
-                                               outgoing_offer_codec_prefs = prefer: configured, operation: union, keep: first, transcode: prevent
+                                               codec_prefs_outgoing_offer = prefer: configured, operation: union, keep: first, transcode: prevent
                                                </example>
                                                <para>
                                                Prefer the codecs coming from the endpoint.  Merge them with the codecs from the core
                                                </para>
                                        </description>
                                </configOption>
-                               <configOption name="incoming_answer_codec_prefs">
+                               <configOption name="codec_prefs_incoming_answer">
                                        <synopsis>Codec negotiation prefs for incoming answers.</synopsis>
                                        <description>
                                                <para>
                                                <para>
                                                </para>
                                                <example>
-                                               incoming_answer_codec_prefs = keep: first
+                                               codec_prefs_incoming_answer = keep: first
                                                </example>
                                                <para>
                                                Use the defaults but keep oinly the first codec.
                                                </para>
                                        </description>
                                </configOption>
-                               <configOption name="outgoing_answer_codec_prefs">
+                               <configOption name="codec_prefs_outgoing_answer">
                                        <synopsis>Codec negotiation prefs for outgoing answers.</synopsis>
                                        <description>
                                                <para>
                                                <para>
                                                </para>
                                                <example>
-                                               incoming_answer_codec_prefs = keep: first
+                                               codec_prefs_incoming_answer = keep: first
                                                </example>
                                                <para>
                                                Use the defaults but keep oinly the first codec.
                                        <synopsis>Send the Diversion header, conveying the diversion
                                        information to the called user agent</synopsis>
                                </configOption>
+                               <configOption name="send_history_info" default="no">
+                                       <synopsis>Send the History-Info header, conveying the diversion
+                                       information to the called and calling user agents</synopsis>
+                               </configOption>
                                <configOption name="send_pai" default="no">
                                        <synopsis>Send the P-Asserted-Identity header</synopsis>
                                </configOption>
index 89ae3e1..344f319 100644 (file)
@@ -1170,6 +1170,7 @@ static int codec_prefs_handler(const struct aco_option *opt,
        struct ast_variable *var, void *obj)
 {
        struct ast_sip_endpoint *endpoint = obj;
+       struct ast_stream_codec_negotiation_prefs *option_prefs;
        struct ast_stream_codec_negotiation_prefs prefs;
        struct ast_str *error_message = ast_str_create(128);
        enum ast_stream_codec_negotiation_prefs_prefer_values default_prefer;
@@ -1185,7 +1186,7 @@ static int codec_prefs_handler(const struct aco_option *opt,
        }
        ast_free(error_message);
 
-       if (strcmp(var->name, "incoming_offer_codec_prefs") == 0) {
+       if (strcmp(var->name, "codec_prefs_incoming_offer") == 0) {
                if (prefs.operation == CODEC_NEGOTIATION_OPERATION_UNION) {
                        ast_log(LOG_ERROR, "Endpoint '%s': Codec preference '%s' has invalid value '%s' for option: '%s'",
                                ast_sorcery_object_get_id(endpoint),
@@ -1194,21 +1195,26 @@ static int codec_prefs_handler(const struct aco_option *opt,
                                var->name);
                        return -1;
                }
-               endpoint->media.incoming_offer_codec_prefs = prefs;
+               option_prefs = &endpoint->media.codec_prefs_incoming_offer;
                default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
                default_operation = CODEC_NEGOTIATION_OPERATION_INTERSECT;
-       } else if (strcmp(var->name, "outgoing_offer_codec_prefs") == 0) {
-               endpoint->media.outgoing_offer_codec_prefs = prefs;
+       } else if (strcmp(var->name, "codec_prefs_outgoing_offer") == 0) {
+               option_prefs = &endpoint->media.codec_prefs_outgoing_offer;
                default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
                default_operation = CODEC_NEGOTIATION_OPERATION_UNION;
-       } else if (strcmp(var->name, "incoming_answer_codec_prefs") == 0) {
-               endpoint->media.incoming_answer_codec_prefs = prefs;
+       } else if (strcmp(var->name, "codec_prefs_incoming_answer") == 0) {
+               option_prefs = &endpoint->media.codec_prefs_incoming_answer;
                default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
                default_operation = CODEC_NEGOTIATION_OPERATION_INTERSECT;
-       } else if (strcmp(var->name, "outgoing_answer_codec_prefs") == 0) {
-               endpoint->media.outgoing_answer_codec_prefs = prefs;
+       } else if (strcmp(var->name, "codec_prefs_outgoing_answer") == 0) {
+               option_prefs = &endpoint->media.codec_prefs_outgoing_answer;
                default_prefer = CODEC_NEGOTIATION_PREFER_PENDING;
                default_operation = CODEC_NEGOTIATION_OPERATION_INTERSECT;
+       } else {
+               ast_log(LOG_ERROR, "Endpoint '%s': Unsupported option '%s'\n",
+                       ast_sorcery_object_get_id(endpoint),
+                       var->name);
+               return -1;
        }
 
        if (prefs.prefer == CODEC_NEGOTIATION_PREFER_UNSPECIFIED) {
@@ -1227,6 +1233,11 @@ static int codec_prefs_handler(const struct aco_option *opt,
                prefs.transcode = CODEC_NEGOTIATION_TRANSCODE_ALLOW;
        }
 
+       /* Now that defaults have been applied as needed we apply the full codec
+        * preference configuration to the option.
+        */
+       *option_prefs = prefs;
+
        return 0;
 }
 
@@ -1248,25 +1259,25 @@ static int codec_prefs_to_str(const struct ast_stream_codec_negotiation_prefs *p
 static int incoming_offer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf)
 {
        const struct ast_sip_endpoint *endpoint = obj;
-       return codec_prefs_to_str(&endpoint->media.incoming_offer_codec_prefs, obj, args, buf);
+       return codec_prefs_to_str(&endpoint->media.codec_prefs_incoming_offer, obj, args, buf);
 }
 
 static int outgoing_offer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf)
 {
        const struct ast_sip_endpoint *endpoint = obj;
-       return codec_prefs_to_str(&endpoint->media.outgoing_offer_codec_prefs, obj, args, buf);
+       return codec_prefs_to_str(&endpoint->media.codec_prefs_outgoing_offer, obj, args, buf);
 }
 
 static int incoming_answer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf)
 {
        const struct ast_sip_endpoint *endpoint = obj;
-       return codec_prefs_to_str(&endpoint->media.incoming_answer_codec_prefs, obj, args, buf);
+       return codec_prefs_to_str(&endpoint->media.codec_prefs_incoming_answer, obj, args, buf);
 }
 
 static int outgoing_answer_codec_prefs_to_str(const void *obj, const intptr_t *args, char **buf)
 {
        const struct ast_sip_endpoint *endpoint = obj;
-       return codec_prefs_to_str(&endpoint->media.outgoing_answer_codec_prefs, obj, args, buf);
+       return codec_prefs_to_str(&endpoint->media.codec_prefs_outgoing_answer, obj, args, buf);
 }
 
 static void *sip_nat_hook_alloc(const char *name)
@@ -2040,6 +2051,7 @@ int ast_res_pjsip_initialize_configuration(void)
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_rpid", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_rpid));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "rpid_immediate", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.rpid_immediate));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_diversion", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_diversion));
+       ast_sorcery_object_field_register(sip_sorcery, "endpoint", "send_history_info", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, id.send_history_info));
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "mailboxes", "", OPT_STRINGFIELD_T, 0, STRFLDSET(struct ast_sip_endpoint, subscription.mwi.mailboxes));
        ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "voicemail_extension", "", voicemail_extension_handler, voicemail_extension_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "aggregate_mwi", "yes", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, subscription.mwi.aggregate));
@@ -2128,16 +2140,16 @@ int ast_res_pjsip_initialize_configuration(void)
                call_offer_pref_handler, incoming_call_offer_pref_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outgoing_call_offer_pref", "remote",
                call_offer_pref_handler, outgoing_call_offer_pref_to_str, NULL, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "incoming_offer_codec_prefs",
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "codec_prefs_incoming_offer",
                "prefer: pending, operation: intersect, keep: all, transcode: allow",
                codec_prefs_handler, incoming_offer_codec_prefs_to_str, NULL, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outgoing_offer_codec_prefs",
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "codec_prefs_outgoing_offer",
                "prefer: pending, operation: union, keep: all, transcode: allow",
                codec_prefs_handler, outgoing_offer_codec_prefs_to_str, NULL, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "incoming_answer_codec_prefs",
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "codec_prefs_incoming_answer",
                "prefer: pending, operation: intersect, keep: all",
                codec_prefs_handler, incoming_answer_codec_prefs_to_str, NULL, 0, 0);
-       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "outgoing_answer_codec_prefs",
+       ast_sorcery_object_field_register_custom(sip_sorcery, "endpoint", "codec_prefs_outgoing_answer",
                "prefer: pending, operation: intersect, keep: all",
                codec_prefs_handler, outgoing_answer_codec_prefs_to_str, NULL, 0, 0);
        ast_sorcery_object_field_register(sip_sorcery, "endpoint", "stir_shaken", "no", OPT_BOOL_T, 1, FLDSET(struct ast_sip_endpoint, stir_shaken));
index 96a9069..9c69f52 100644 (file)
@@ -36,6 +36,8 @@
 #include "asterisk/strings.h"
 
 static const pj_str_t diversion_name = { "Diversion", 9 };
+static const pj_str_t history_info_name = { "History-Info", 12 };
+static pj_str_t HISTINFO_SUPPORTED_NAME = { "histinfo", 8 };
 
 /*!
  * \internal
@@ -80,22 +82,62 @@ static int sip_is_token(const char *str)
 static const struct reasons {
        enum AST_REDIRECTING_REASON code;
        const char *text;
+       const unsigned int cause;
 } reason_table[] = {
-       { AST_REDIRECTING_REASON_UNKNOWN, "unknown" },
-       { AST_REDIRECTING_REASON_USER_BUSY, "user-busy" },
-       { AST_REDIRECTING_REASON_NO_ANSWER, "no-answer" },
-       { AST_REDIRECTING_REASON_UNAVAILABLE, "unavailable" },
-       { AST_REDIRECTING_REASON_UNCONDITIONAL, "unconditional" },
-       { AST_REDIRECTING_REASON_TIME_OF_DAY, "time-of-day" },
-       { AST_REDIRECTING_REASON_DO_NOT_DISTURB, "do-not-disturb" },
-       { AST_REDIRECTING_REASON_DEFLECTION, "deflection" },
-       { AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me" },
-       { AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service" },
-       { AST_REDIRECTING_REASON_AWAY, "away" },
-       { AST_REDIRECTING_REASON_CALL_FWD_DTE, "cf_dte" },              /* Non-standard */
-       { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm" },    /* Non-standard */
+       { AST_REDIRECTING_REASON_UNKNOWN, "unknown", 404 },
+       { AST_REDIRECTING_REASON_USER_BUSY, "user-busy", 486 },
+       { AST_REDIRECTING_REASON_NO_ANSWER, "no-answer", 408 },
+       { AST_REDIRECTING_REASON_UNAVAILABLE, "unavailable", 503 },
+       { AST_REDIRECTING_REASON_UNCONDITIONAL, "unconditional", 302 },
+       { AST_REDIRECTING_REASON_TIME_OF_DAY, "time-of-day", 404 },
+       { AST_REDIRECTING_REASON_DO_NOT_DISTURB, "do-not-disturb", 404 },
+       { AST_REDIRECTING_REASON_DEFLECTION, "deflection", 480 },
+       { AST_REDIRECTING_REASON_FOLLOW_ME, "follow-me", 404 },
+       { AST_REDIRECTING_REASON_OUT_OF_ORDER, "out-of-service", 404 },
+       { AST_REDIRECTING_REASON_AWAY, "away", 404 },
+       { AST_REDIRECTING_REASON_CALL_FWD_DTE, "cf_dte", 404 },         /* Non-standard */
+       { AST_REDIRECTING_REASON_SEND_TO_VM, "send_to_vm", 404 },       /* Non-standard */
 };
 
+static enum AST_REDIRECTING_REASON cause_to_reason(const unsigned long cause) {
+       switch(cause) {
+               case 302:
+                       return AST_REDIRECTING_REASON_UNCONDITIONAL;
+               case 486:
+                       return AST_REDIRECTING_REASON_USER_BUSY;
+               case 408:
+                       return AST_REDIRECTING_REASON_NO_ANSWER;
+               case 480:
+               case 487:
+                       return AST_REDIRECTING_REASON_DEFLECTION;
+               case 503:
+                       return AST_REDIRECTING_REASON_UNAVAILABLE;
+               default:
+                       return AST_REDIRECTING_REASON_UNKNOWN;
+       }
+}
+
+static int add_supported(pjsip_tx_data *tdata)
+{
+       pjsip_supported_hdr *hdr;
+
+       hdr = pjsip_msg_find_hdr(tdata->msg, PJSIP_H_SUPPORTED, NULL);
+       if (!hdr) {
+               /* insert a new Supported header */
+               hdr = pjsip_supported_hdr_create(tdata->pool);
+               if (!hdr) {
+                       return -1;
+               }
+
+               pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
+       }
+
+       /* add on to the existing Supported header */
+       pj_strassign(&hdr->values[hdr->count++], &HISTINFO_SUPPORTED_NAME);
+
+       return 0;
+}
+
 static const char *reason_code_to_str(const struct ast_party_redirecting_reason *reason)
 {
        int idx;
@@ -116,6 +158,21 @@ static const char *reason_code_to_str(const struct ast_party_redirecting_reason
        return "unknown";
 }
 
+static const unsigned int reason_code_to_cause(const struct ast_party_redirecting_reason *reason)
+{
+       int idx;
+       int code;
+
+       code = reason->code;
+       for (idx = 0; idx < ARRAY_LEN(reason_table); ++idx) {
+               if (code == reason_table[idx].code) {
+                       return reason_table[idx].cause;
+               }
+       }
+
+       return 404;
+}
+
 static pjsip_fromto_hdr *get_diversion_header(pjsip_rx_data *rdata)
 {
        static const pj_str_t from_name = { "From", 4 };
@@ -135,6 +192,61 @@ static pjsip_fromto_hdr *get_diversion_header(pjsip_rx_data *rdata)
                               pj_strlen(&value), &size);
 }
 
+/* Asterisk keeps track of 2 things. The redirected from address and
+ * the redirected to address. If first=0 method will get the most recent
+ * redirection target for use as the redirected to address. If first=1
+ * then this method will get the original redirection target (index=1)
+ * for use as the redirected from address.
+ */
+static pjsip_fromto_hdr *get_history_info_header(pjsip_rx_data *rdata, const unsigned int first)
+{
+       static const pj_str_t from_name = { "From", 4 };
+       pjsip_fromto_hdr * result_hdr = NULL;
+
+       pjsip_generic_string_hdr *hdr = NULL;
+
+       hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &history_info_name, NULL);
+
+       if (!hdr) {
+               return NULL;
+       }
+
+       do {
+               static const pj_str_t index_name = { "index", 5 };
+               pj_str_t value;
+               int size;
+               pjsip_fromto_hdr * fromto_hdr = NULL;
+               pjsip_param * index = NULL;
+
+               pj_strdup_with_null(rdata->tp_info.pool, &value, &hdr->hvalue);
+
+               /* parse as a fromto header */
+               fromto_hdr =  pjsip_parse_hdr(rdata->tp_info.pool, &from_name, value.ptr,
+                                      pj_strlen(&value), &size);
+
+               if (fromto_hdr == NULL) {
+                       continue;
+               }
+
+               index = pjsip_param_find(&fromto_hdr->other_param, &index_name);
+
+               if (index) {
+                       if (!pj_strcmp2(&index->value, "1")) {
+                               if (!first) {
+                                       continue;
+                               } else {
+                                       return fromto_hdr;
+                               }
+                       }
+               }
+
+               result_hdr = fromto_hdr;
+
+       } while ((hdr = pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &history_info_name, hdr->next)));
+
+       return result_hdr;
+}
+
 static void set_redirecting_value(char **dst, const pj_str_t *src)
 {
        ast_free(*dst);
@@ -197,14 +309,36 @@ static void copy_redirecting_id(struct ast_party_id *dst, const struct ast_party
        }
 }
 
-static void set_redirecting_reason(pjsip_fromto_hdr *hdr,
+static void set_redirecting_reason_by_cause(pjsip_name_addr *name_addr,
+                                  struct ast_party_redirecting_reason *data)
+{
+       static const pj_str_t cause_name = { "cause", 5 };
+       pjsip_sip_uri *uri = pjsip_uri_get_uri(name_addr);
+       pjsip_param *cause = pjsip_param_find(&uri->other_param, &cause_name);
+       unsigned long cause_value;
+
+       if (!cause) {
+               return;
+       }
+
+       cause_value = pj_strtoul(&cause->value);
+
+       data->code = cause_to_reason(cause_value);
+       ast_free(data->str);
+       data->str = ast_strdup("");
+}
+
+static void set_redirecting_reason(pjsip_fromto_hdr *from_info, pjsip_name_addr *to_info,
                                   struct ast_party_redirecting_reason *data)
 {
        static const pj_str_t reason_name = { "reason", 6 };
-       pjsip_param *reason = pjsip_param_find(&hdr->other_param, &reason_name);
+       pjsip_param *reason = pjsip_param_find(&from_info->other_param, &reason_name);
        char *reason_str;
 
        if (!reason) {
+               if (to_info) {
+                       set_redirecting_reason_by_cause(to_info, data);
+               }
                return;
        }
 
@@ -246,12 +380,15 @@ static void set_redirecting(struct ast_sip_session *session,
        if (from_info) {
                set_redirecting_id((pjsip_name_addr*)from_info->uri,
                        &data.from, &update.from);
-               set_redirecting_reason(from_info, &data.reason);
+               set_redirecting_reason(from_info, to_info, &data.reason);
+               ast_set_party_id_all(&update.priv_to);
        } else {
                copy_redirecting_id(&data.from, &session->id, &update.from);
        }
 
-       set_redirecting_id(to_info, &data.to, &update.to);
+       if (to_info) {
+               set_redirecting_id(to_info, &data.to, &update.to);
+       }
 
        ast_set_party_id_all(&update.priv_orig);
        ast_set_party_id_all(&update.priv_from);
@@ -259,6 +396,10 @@ static void set_redirecting(struct ast_sip_session *session,
        ++data.count;
 
        ast_channel_set_redirecting(session->channel, &data, &update);
+       /* Only queue an indication if it was due to a response */
+       if (session->inv_session->role == PJSIP_ROLE_UAC) {
+               ast_channel_queue_redirecting_update(session->channel, &data, &update);
+       }
        ast_party_redirecting_free(&data);
 }
 
@@ -269,6 +410,17 @@ static int diversion_incoming_request(struct ast_sip_session *session, pjsip_rx_
        if (hdr) {
                set_redirecting(session, hdr, (pjsip_name_addr*)
                                PJSIP_MSG_TO_HDR(rdata->msg_info.msg)->uri);
+       } else {
+               pjsip_fromto_hdr *history_info_to;
+               pjsip_fromto_hdr *history_info_from;
+               history_info_to  = get_history_info_header(rdata, 0);
+
+               if (history_info_to) {
+                       /* If History-Info is present, then it will also include the original
+                          redirected-from in addition to the redirected-to */
+                       history_info_from = get_history_info_header(rdata, 1);
+                       set_redirecting(session, history_info_from, (pjsip_name_addr*)history_info_to->uri);
+               }
        }
 
        return 0;
@@ -281,6 +433,8 @@ static void diversion_incoming_response(struct ast_sip_session *session, pjsip_r
 
        pjsip_status_line status = rdata->msg_info.msg->line.status;
        pjsip_fromto_hdr *div_hdr;
+       pjsip_fromto_hdr *history_info_to;
+       pjsip_fromto_hdr *history_info_from;
        pjsip_contact_hdr *contact_hdr;
 
        if ((status.code != 302) && (status.code != 181)) {
@@ -288,15 +442,35 @@ static void diversion_incoming_response(struct ast_sip_session *session, pjsip_r
        }
 
        /* use the diversion header info if there is one. if not one then use the
-           session caller id info. if that doesn't exist use info from the To hdr*/
-       if (!(div_hdr = get_diversion_header(rdata)) && !session->id.number.valid) {
-               div_hdr = PJSIP_MSG_TO_HDR(rdata->msg_info.msg);
+          the history-info, if that doesn't exist, use session caller id info. if
+          that doesn't exist use info from the To hdr*/
+       if (!(div_hdr = get_diversion_header(rdata))) {
+               history_info_to  = get_history_info_header(rdata, 0);
+
+               if (history_info_to) {
+                       /* If History-Info is present, then it will also include the original
+                          redirected-from in addition to the redirected-to */
+                       history_info_from = get_history_info_header(rdata, 1);
+                       set_redirecting(session, history_info_from, (pjsip_name_addr*)history_info_to->uri);
+                       return;
+               }
+               if (!div_hdr && !session->id.number.valid) {
+                       div_hdr = PJSIP_MSG_TO_HDR(rdata->msg_info.msg);
+               }
        }
 
-       contact_hdr = pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &contact_name, &contact_name_s, NULL);
 
-       set_redirecting(session, div_hdr, contact_hdr ? (pjsip_name_addr*)contact_hdr->uri :
-                       (pjsip_name_addr*)PJSIP_MSG_FROM_HDR(rdata->msg_info.msg)->uri);
+       if (status.code == 302) {
+               /* With 302, Contact indicates the final destination and possibly Diversion indicates the hop before */
+               contact_hdr = pjsip_msg_find_hdr_by_names(rdata->msg_info.msg, &contact_name, &contact_name_s, NULL);
+
+               set_redirecting(session, div_hdr, contact_hdr ? (pjsip_name_addr*)contact_hdr->uri :
+                               (pjsip_name_addr*)PJSIP_MSG_FROM_HDR(rdata->msg_info.msg)->uri);
+       } else {
+               /* With 181, Diversion is non-standard, but if present indicates the new final destination, and To indicating the original */
+               set_redirecting(session, PJSIP_MSG_TO_HDR(rdata->msg_info.msg),
+                               div_hdr ? (pjsip_name_addr*)div_hdr->uri : NULL);
+       }
 }
 
 /*!
@@ -308,6 +482,8 @@ static void diversion_incoming_response(struct ast_sip_session *session, pjsip_r
  */
 static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirecting *data)
 {
+       static const pj_str_t reason_name = { "reason", 6 };
+
        pjsip_fromto_hdr *hdr;
        pjsip_name_addr *name_addr;
        pjsip_sip_uri *uri;
@@ -316,9 +492,17 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
        const char *reason_str;
        const char *quote_str;
        char *reason_buf;
+       pjsip_uri *base;
 
-       struct ast_party_id *id = &data->from;
-       pjsip_uri *base = PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
+       struct ast_party_id *id = NULL;
+       if (tdata->msg->type == PJSIP_REQUEST_MSG) {
+               id = &data->from;
+       } else {
+               /* In responses indicate the new destination */
+               id = &data->to;
+       }
+
+       base = PJSIP_MSG_FROM_HDR(tdata->msg)->uri;
 
        if (!id->number.valid || ast_strlen_zero(id->number.str)) {
                return;
@@ -335,7 +519,7 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
        pj_strdup2(tdata->pool, &uri->user, id->number.str);
 
        param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
-       param->name = pj_str("reason");
+       param->name = reason_name;
 
        reason_str = reason_code_to_str(&data->reason);
 
@@ -357,14 +541,106 @@ static void add_diversion_header(pjsip_tx_data *tdata, struct ast_party_redirect
        pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
 }
 
+/*!
+ * \internal
+ * \brief Adds history-info header information to an outbound SIP message
+ *
+ * \param tdata The outbound message
+ * \param data The redirecting data used to fill parts of the history-info header
+ */
+static void add_history_info_header(pjsip_tx_data *tdata, struct ast_party_redirecting *data)
+{
+       static const pj_str_t index_name = { "index", 5 };
+       static const pj_str_t cause_name = { "cause", 5 };
+       static const pj_str_t first_index = { "1", 1 };
+       static const pj_str_t last_index = { "1.1", 3 };
+
+       pjsip_fromto_hdr *hdr;
+       pjsip_name_addr *name_addr;
+       pjsip_sip_uri *uri;
+       pjsip_param *param;
+       pjsip_fromto_hdr *old_hdr;
+       unsigned int cause;
+       char *cause_buf;
+
+       struct ast_party_id *to = &data->to;
+       struct ast_party_id *from = &data->from;
+
+       pjsip_uri *base = PJSIP_MSG_TO_HDR(tdata->msg)->uri;
+
+
+       hdr = pjsip_from_hdr_create(tdata->pool);
+       hdr->type = PJSIP_H_OTHER;
+       hdr->sname = hdr->name = history_info_name;
+
+       name_addr = pjsip_uri_clone(tdata->pool, base);
+       uri = pjsip_uri_get_uri(name_addr->uri);
+
+       /* if no redirecting information, then TO is the original destination */
+       if (from->number.valid && !ast_strlen_zero(from->number.str)) {
+               pj_strdup2(tdata->pool, &name_addr->display, from->name.str);
+               pj_strdup2(tdata->pool, &uri->user, from->number.str);
+       }
+
+       param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
+       param->name = index_name;
+       param->value = first_index;
+
+
+       pj_list_insert_before(&hdr->other_param, param);
+       hdr->uri = (pjsip_uri *) name_addr;
+
+       while ((old_hdr = pjsip_msg_find_hdr_by_name(tdata->msg, &history_info_name, NULL)) != NULL) {
+               pj_list_erase(old_hdr);
+       }
+
+       pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
+
+       if (!to->number.valid || ast_strlen_zero(to->number.str)) {
+               return;
+       }
+
+       hdr = pjsip_from_hdr_create(tdata->pool);
+       hdr->type = PJSIP_H_OTHER;
+       hdr->sname = hdr->name = history_info_name;
+
+       name_addr = pjsip_uri_clone(tdata->pool, base);
+       uri = pjsip_uri_get_uri(name_addr->uri);
+
+       pj_strdup2(tdata->pool, &name_addr->display, to->name.str);
+       pj_strdup2(tdata->pool, &uri->user, to->number.str);
+
+       param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
+       param->name = index_name;
+       param->value = last_index;
+       pj_list_insert_before(&hdr->other_param, param);
+
+       param = PJ_POOL_ALLOC_T(tdata->pool, pjsip_param);
+       param->name = cause_name;
+       cause = reason_code_to_cause(&data->reason);
+       cause_buf = pj_pool_alloc(tdata->pool, 4);
+       snprintf(cause_buf, 4, "%ud", cause);
+       param->value = pj_str(cause_buf);
+       pj_list_insert_before(&uri->other_param, param);
+       hdr->uri = (pjsip_uri *) name_addr;
+
+       pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr *)hdr);
+}
+
 static void get_redirecting_add_diversion(struct ast_sip_session *session, pjsip_tx_data *tdata)
 {
        struct ast_party_redirecting *data;
 
+       add_supported(tdata);
+
        if (session->channel && session->endpoint->id.send_diversion &&
            (data = ast_channel_redirecting(session->channel))->count) {
                add_diversion_header(tdata, data);
        }
+       if (session->channel && session->endpoint->id.send_history_info) {
+               data = ast_channel_redirecting(session->channel);
+               add_history_info_header(tdata, data);
+       }
 }
 
 /*!
@@ -406,7 +682,7 @@ static struct ast_sip_session_supplement diversion_supplement = {
        .incoming_response = diversion_incoming_response,
        .outgoing_request = diversion_outgoing_request,
        .outgoing_response = diversion_outgoing_response,
-       .response_priority = AST_SIP_SESSION_BEFORE_REDIRECTING,
+       .response_priority = AST_SIP_SESSION_BEFORE_REDIRECTING|AST_SIP_SESSION_BEFORE_MEDIA,
 };
 
 static int load_module(void)
index bdca887..e970f66 100644 (file)
@@ -76,7 +76,8 @@ static int sip_session_refresh(struct ast_sip_session *session,
                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,
-               struct ast_sip_session_media_state *media_state,
+               struct ast_sip_session_media_state *pending_media_state,
+               struct ast_sip_session_media_state *active_media_state,
                int queued);
 
 /*! \brief NAT hook for modifying outgoing messages with SDP */
@@ -482,18 +483,24 @@ static void session_media_dtor(void *obj)
 
        ast_free(session_media->mid);
        ast_free(session_media->remote_mslabel);
+       ast_free(session_media->remote_label);
+       ast_free(session_media->stream_name);
 }
 
 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;
+       SCOPE_ENTER(1, "%s Adding position %d\n", ast_sip_session_get_name(session), position);
 
        /* 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);
+               session_media = AST_VECTOR_GET(&media_state->sessions, position);
+               if (session_media) {
+                       SCOPE_EXIT_RTN_VALUE(session_media, "Using existing media_session\n");
+               }
        }
 
        /* Determine if we can reuse the session media from the active media state if present */
@@ -502,6 +509,7 @@ struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_ses
                /* A stream can never exist without an accompanying media session */
                if (session_media->type == type) {
                        ao2_ref(session_media, +1);
+                       ast_trace(1, "Reusing existing media session\n");
                        /*
                         * If this session_media was previously removed, its bundle group was probably reset
                         * to -1 so if bundling is enabled on the endpoint, we need to reset it to 0, set
@@ -513,10 +521,12 @@ struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_ses
                                ast_free(session_media->mid);
                                if (ast_asprintf(&session_media->mid, "%s-%d", ast_codec_media_type2str(type), position) < 0) {
                                        ao2_ref(session_media, -1);
-                                       return NULL;
+                                       SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't alloc mid\n");
                                }
                        }
                } else {
+                       ast_trace(1, "Can't reuse existing media session because the types are different. %s <> %s\n",
+                               ast_codec_media_type2str(type), ast_codec_media_type2str(session_media->type));
                        session_media = NULL;
                }
        }
@@ -527,6 +537,7 @@ struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_ses
                if (!session_media) {
                        return NULL;
                }
+               ast_trace(1, "Creating new media session\n");
 
                session_media->encryption = session->endpoint->media.rtp.encryption;
                session_media->remote_ice = session->endpoint->media.rtp.ice_support;
@@ -542,7 +553,7 @@ struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_ses
                         */
                        if (ast_asprintf(&session_media->mid, "%s-%d", ast_codec_media_type2str(type), position) < 0) {
                                ao2_ref(session_media, -1);
-                               return NULL;
+                               SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't alloc mid\n");
                        }
                        session_media->bundle_group = 0;
 
@@ -556,18 +567,23 @@ struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_ses
                }
        }
 
+       ast_free(session_media->stream_name);
+       session_media->stream_name = ast_strdup(ast_stream_get_name(ast_stream_topology_get_stream(media_state->topology, position)));
+
        if (AST_VECTOR_REPLACE(&media_state->sessions, position, session_media)) {
                ao2_ref(session_media, -1);
 
-               return NULL;
+               SCOPE_EXIT_RTN_VALUE(NULL, "Couldn't replace media_session\n");
        }
 
        /* 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) {
+               ast_trace(1, "Setting media session as default for %s\n", ast_codec_media_type2str(session_media->type));
+
                media_state->default_session[type] = session_media;
        }
 
-       return session_media;
+       SCOPE_EXIT_RTN_VALUE(session_media, "Done\n");
 }
 
 static int is_stream_limitation_reached(enum ast_media_type type, const struct ast_sip_endpoint *endpoint, int *type_streams)
@@ -672,6 +688,8 @@ static void set_remote_mslabel_and_stream_group(struct ast_sip_session *session,
 
        ast_free(session_media->remote_mslabel);
        session_media->remote_mslabel = NULL;
+       ast_free(session_media->remote_label);
+       session_media->remote_label = NULL;
 
        for (index = 0; index < stream->attr_count; ++index) {
                pjmedia_sdp_attr *attr = stream->attr[index];
@@ -680,8 +698,12 @@ static void set_remote_mslabel_and_stream_group(struct ast_sip_session *session,
                char *msid, *tmp = attr_value;
                static const pj_str_t STR_msid = { "msid", 4 };
                static const pj_str_t STR_ssrc = { "ssrc", 4 };
+               static const pj_str_t STR_label = { "label", 5 };
 
-               if (!pj_strcmp(&attr->name, &STR_msid)) {
+               if (!pj_strcmp(&attr->name, &STR_label)) {
+                       ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value));
+                       session_media->remote_label = ast_strdup(attr_value);
+               } else if (!pj_strcmp(&attr->name, &STR_msid)) {
                        ast_copy_pj_str(attr_value, &attr->value, sizeof(attr_value));
                        msid = strsep(&tmp, " ");
                        session_media->remote_mslabel = ast_strdup(msid);
@@ -741,18 +763,19 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
        int i;
        int handled = 0;
        int type_streams[AST_MEDIA_TYPE_END] = {0};
-       SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
+       SCOPE_ENTER(3, "%s: Media count: %d\n", ast_sip_session_get_name(session), sdp->media_count);
 
        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");
-               SCOPE_EXIT_RTN_VALUE(-1, "Already disconnected\n");
+               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Failed to handle incoming SDP. Session has been already disconnected\n",
+                       ast_sip_session_get_name(session));
        }
 
        /* 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) {
-                       SCOPE_EXIT_RTN_VALUE(-1, "No topology\n");
+                       SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "%s: Couldn't alloc pending topology\n",
+                               ast_sip_session_get_name(session));
                }
        }
 
@@ -766,6 +789,7 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
                enum ast_media_type type;
                struct ast_stream *stream = NULL;
                pjmedia_sdp_media *remote_stream = sdp->media[i];
+               SCOPE_ENTER(4, "%s: Processing stream %d\n", ast_sip_session_get_name(session), i);
 
                /* We need a null-terminated version of the media string */
                ast_copy_pj_str(media, &sdp->media[i]->desc.media, sizeof(media));
@@ -774,29 +798,56 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
                /* 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);
+                       ast_trace(-1, "%s: Using existing pending stream %s\n", ast_sip_session_get_name(session),
+                               ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
                }
                if (!stream) {
                        struct ast_stream *existing_stream = NULL;
+                       char *stream_name = NULL;
+                       const char *stream_label = NULL;
 
                        if (session->active_media_state->topology &&
                                (i < ast_stream_topology_get_count(session->active_media_state->topology))) {
                                existing_stream = ast_stream_topology_get_stream(session->active_media_state->topology, i);
+                               ast_trace(-1, "%s: Found existing active stream %s\n", ast_sip_session_get_name(session),
+                                       ast_str_tmp(128, ast_stream_to_str(existing_stream, &STR_TMP)));
+
+                               if (ast_stream_get_state(existing_stream) != AST_STREAM_STATE_REMOVED) {
+                                       stream_name = (char *)ast_stream_get_name(existing_stream);
+                                       stream_label = ast_stream_get_metadata(existing_stream, "SDP:LABEL");
+                               }
+                       }
+
+                       if (ast_strlen_zero(stream_name)) {
+                               if (ast_asprintf(&stream_name, "%s-%d", ast_codec_media_type2str(type), i) < 0) {
+                                       handled = 0;
+                                       SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't alloc stream name\n",
+                                                ast_sip_session_get_name(session));
+
+                               }
+                               ast_trace(-1, "%s: Using %s for new stream name\n", ast_sip_session_get_name(session),
+                                       stream_name);
                        }
 
-                       stream = ast_stream_alloc(existing_stream ? ast_stream_get_name(existing_stream) : ast_codec_media_type2str(type), type);
+                       stream = ast_stream_alloc(stream_name, type);
                        if (!stream) {
-                               SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create stream\n");
+                               handled = 0;
+                               SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't alloc stream\n",
+                                        ast_sip_session_get_name(session));
                        }
-                       if (ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream)) {
-                               ast_stream_free(stream);
-                               SCOPE_EXIT_RTN_VALUE(-1, "Couldn't set stream\n");
+
+                       if (!ast_strlen_zero(stream_label)) {
+                               ast_stream_set_metadata(stream, "SDP:LABEL", stream_label);
+                               ast_trace(-1, "%s: Using %s for new stream label\n", ast_sip_session_get_name(session),
+                                       stream_label);
+
                        }
-                       if (existing_stream) {
-                               const char *stream_label = ast_stream_get_metadata(existing_stream, "SDP:LABEL");
 
-                               if (!ast_strlen_zero(stream_label)) {
-                                       ast_stream_set_metadata(stream, "SDP:LABEL", stream_label);
-                               }
+                       if (ast_stream_topology_set_stream(session->pending_media_state->topology, i, stream)) {
+                               ast_stream_free(stream);
+                               handled = 0;
+                               SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't set stream in topology\n",
+                                        ast_sip_session_get_name(session));
                        }
 
                        /* For backwards compatibility with the core the default audio stream is always sendrecv */
@@ -817,19 +868,21 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
                        } else {
                                ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV);
                        }
+                       ast_trace(-1, "%s: Using new stream %s\n", ast_sip_session_get_name(session),
+                               ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
                }
 
                session_media = ast_sip_session_media_state_add(session, session->pending_media_state, ast_media_type_from_str(media), i);
                if (!session_media) {
-                       SCOPE_EXIT_RTN_VALUE(-1, "Couldn't add session media\n");
+                       SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't alloc session media\n",
+                                ast_sip_session_get_name(session));
                }
 
                /* 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);
                        remove_stream_from_bundle(session_media, stream);
-                       continue;
+                       SCOPE_EXIT_EXPR(continue, "%s: Declining incoming SDP media stream %s'\n",
+                               ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
                }
 
                set_mid_and_bundle_group(session, session_media, sdp, remote_stream);
@@ -837,69 +890,70 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd
 
                if (session_media->handler) {
                        handler = session_media->handler;
-                       ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
-                               ast_codec_media_type2str(session_media->type),
+                       ast_trace(-1, "%s: Negotiating incoming SDP media stream %s using %s SDP handler\n",
+                               ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)),
                                session_media->handler->id);
                        res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
                        if (res < 0) {
                                /* Catastrophic failure. Abort! */
-                               SCOPE_EXIT_RTN_VALUE(-1, "Couldn't negotiate incoming sdp stream\n");
+                               SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't negotiate stream %s\n",
+                                        ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
                        } else if (res == 0) {
-                               ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
-                                       ast_codec_media_type2str(type), i);
                                remove_stream_from_bundle(session_media, stream);
-                               continue;
+                               SCOPE_EXIT_EXPR(continue, "%s: Declining incoming SDP media stream %s\n",
+                                       ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
                        } else if (res > 0) {
-                               ast_debug(1, "Media stream '%s' handled by %s\n",
-                                       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;
+                               /* Handled by this handler. Move to the next stream */
+                               SCOPE_EXIT_EXPR(continue, "%s: Media stream %s handled by %s\n",
+                                       ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)),
+                                       session_media->handler->id);
                        }
                }
 
                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);
-                       continue;
+                       SCOPE_EXIT_EXPR(continue, "%s: Media stream %s has no registered handlers\n",
+                               ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
                }
                AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
                        if (handler == session_media->handler) {
                                continue;
                        }
-                       ast_debug(1, "Negotiating incoming SDP media stream '%s' using %s SDP handler\n",
-                               ast_codec_media_type2str(session_media->type),
+                       ast_trace(-1, "%s: Negotiating incoming SDP media stream %s using %s SDP handler\n",
+                               ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)),
                                handler->id);
+
                        res = handler->negotiate_incoming_sdp_stream(session, session_media, sdp, i, stream);
                        if (res < 0) {
                                /* Catastrophic failure. Abort! */
-                               return -1;
+                               handled = 0;
+                               SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Couldn't negotiate stream %s\n",
+                                        ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
                        } else if (res == 0) {
-                               ast_debug(1, "Declining incoming SDP media stream '%s' at position '%d'\n",
-                                       ast_codec_media_type2str(type), i);
                                remove_stream_from_bundle(session_media, stream);
+                               ast_trace(-1, "%s: Declining incoming SDP media stream %s\n",
+                                       ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
                                continue;
                        } else if (res > 0) {
-                               ast_debug(1, "Media stream '%s' handled by %s\n",
-                                       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];
+                               ast_trace(-1, "%s: Media stream %s handled by %s\n",
+                                       ast_sip_session_get_name(session), ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)),
+                                       session_media->handler->id);
                                break;
                        }
                }
+
+               SCOPE_EXIT("%s: Done with stream %s\n", ast_sip_session_get_name(session),
+                       ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
        }
-       if (!handled) {
-               SCOPE_EXIT_RTN_VALUE(-1, "Not handled\n");
-       }
-       SCOPE_EXIT_RTN_VALUE(0, "Handled.  Active: %s  Pending: %s\n",
-               ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP)),
-               ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP))
-               );
+
+end:
+       SCOPE_EXIT_RTN_VALUE(handled ? 0 : -1, "%s: Handled? %s\n", ast_sip_session_get_name(session),
+               handled ? "yes" : "no");
 }
 
 static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *session_media,
@@ -943,13 +997,13 @@ static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *ses
 
        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),
+               ast_debug(4, "%s: Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+                       ast_sip_session_get_name(session), 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),
+                       ast_debug(4, "%s: Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+                               ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type),
                                handler->id);
                        SCOPE_EXIT_RTN_VALUE(0,  "%s: Applied negotiated SDP media stream '%s' using %s SDP handler\n",
                                ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type),
@@ -962,16 +1016,15 @@ static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *ses
 
        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);
-               SCOPE_EXIT_RTN_VALUE(-1, "%s: No registered SDP handlers for media type '%s'\n",
-                       ast_sip_session_get_name(session), media);
+               ast_debug(4, "%s: No registered SDP handlers for media type '%s'\n", ast_sip_session_get_name(session), media);
+               return -1;
        }
        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",
-                       ast_codec_media_type2str(session_media->type),
+               ast_debug(4, "%s: Applying negotiated SDP media stream '%s' using %s SDP handler\n",
+                       ast_sip_session_get_name(session), 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) {
@@ -980,8 +1033,8 @@ static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *ses
                                ast_sip_session_get_name(session), handler->id, res);
                }
                if (res > 0) {
-                       ast_debug(1, "Applied negotiated SDP media stream '%s' using %s SDP handler\n",
-                               ast_codec_media_type2str(session_media->type),
+                       ast_debug(4, "%s: Applied negotiated SDP media stream '%s' using %s SDP handler\n",
+                               ast_sip_session_get_name(session), 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);
@@ -992,9 +1045,8 @@ static int handle_negotiated_sdp_session_media(struct ast_sip_session_media *ses
 
        res = 0;
        if (session_media->handler && session_media->handler->stream_stop) {
-               res = 1;
-               ast_debug(1, "Stopping SDP media stream '%s' as it is not currently negotiated\n",
-                       ast_codec_media_type2str(session_media->type));
+               ast_debug(4, "%s: Stopping SDP media stream '%s' as it is not currently negotiated\n",
+                       ast_sip_session_get_name(session), ast_codec_media_type2str(session_media->type));
                session_media->handler->stream_stop(session_media);
        }
 
@@ -1022,17 +1074,17 @@ static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_
                        active_media_state_clone =
                                ast_sip_session_media_state_clone(session->active_media_state);
                        if (!active_media_state_clone) {
-                               ast_log(LOG_WARNING, "Unable to clone active media state for channel '%s'\n",
-                                       session->channel ? ast_channel_name(session->channel) : "unknown");
-                               SCOPE_EXIT_RTN_VALUE(-1, "Unable to clone active media state\n");
+                               ast_log(LOG_WARNING, "%s: Unable to clone active media state\n",
+                                       ast_sip_session_get_name(session));
+                               return -1;
                        }
 
                        ast_sip_session_media_state_free(session->pending_media_state);
                        session->pending_media_state = active_media_state_clone;
                } else {
-                       ast_log(LOG_WARNING, "No pending or active media state for channel '%s'\n",
-                               session->channel ? ast_channel_name(session->channel) : "unknown");
-                       SCOPE_EXIT_RTN_VALUE(-1, "No media state\n");
+                       ast_log(LOG_WARNING, "%s: No pending or active media state\n",
+                               ast_sip_session_get_name(session));
+                       return -1;
                }
        }
 
@@ -1043,8 +1095,8 @@ static int handle_negotiated_sdp(struct ast_sip_session *session, const pjmedia_
         */
        if (ast_stream_topology_get_count(session->pending_media_state->topology) != local->media_count
                || AST_VECTOR_SIZE(&session->pending_media_state->sessions) != local->media_count) {
-               ast_log(LOG_WARNING, "Local SDP for channel '%s' contains %d media streams while we expected it to contain %u\n",
-                       session->channel ? ast_channel_name(session->channel) : "unknown",
+               ast_log(LOG_WARNING, "%s: Local SDP contains %d media streams while we expected it to contain %u\n",
+                       ast_sip_session_get_name(session),
                        ast_stream_topology_get_count(session->pending_media_state->topology), local->media_count);
                SCOPE_EXIT_RTN_VALUE(-1, "Media stream count mismatch\n");
        }
@@ -1288,7 +1340,10 @@ struct ast_sip_session_delayed_request {
        /*! Whether to generate new SDP */
        int generate_new_sdp;
        /*! Requested media state for the SDP */
-       struct ast_sip_session_media_state *media_state;
+       struct ast_sip_session_media_state *pending_media_state;
+       /*! Active media state at the time of the original request */
+       struct ast_sip_session_media_state *active_media_state;
+
        AST_LIST_ENTRY(ast_sip_session_delayed_request) next;
 };
 
@@ -1298,7 +1353,8 @@ static struct ast_sip_session_delayed_request *delayed_request_alloc(
        ast_sip_session_sdp_creation_cb on_sdp_creation,
        ast_sip_session_response_cb on_response,
        int generate_new_sdp,
-       struct ast_sip_session_media_state *media_state)
+       struct ast_sip_session_media_state *pending_media_state,
+       struct ast_sip_session_media_state *active_media_state)
 {
        struct ast_sip_session_delayed_request *delay = ast_calloc(1, sizeof(*delay));
 
@@ -1310,13 +1366,15 @@ 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;
+       delay->pending_media_state = pending_media_state;
+       delay->active_media_state = active_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_sip_session_media_state_free(delay->pending_media_state);
+       ast_sip_session_media_state_free(delay->active_media_state);
        ast_free(delay);
 }
 
@@ -1331,34 +1389,37 @@ static void delayed_request_free(struct ast_sip_session_delayed_request *delay)
 static int send_delayed_request(struct ast_sip_session *session, struct ast_sip_session_delayed_request *delay)
 {
        int res;
-
-       ast_debug(3, "Endpoint '%s(%s)' sending delayed %s request.\n",
-               ast_sorcery_object_get_id(session->endpoint),
-               session->channel ? ast_channel_name(session->channel) : "",
+       SCOPE_ENTER(3, "%s: sending delayed %s request\n",
+               ast_sip_session_get_name(session),
                delayed_method2str(delay->method));
 
        switch (delay->method) {
        case DELAYED_METHOD_INVITE:
                res = 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, delay->media_state, 1);
+                       AST_SIP_SESSION_REFRESH_METHOD_INVITE, delay->generate_new_sdp, delay->pending_media_state,
+                       delay->active_media_state, 1);
                /* Ownership of media state transitions to ast_sip_session_refresh */
-               delay->media_state = NULL;
-               return res;
+               delay->pending_media_state = NULL;
+               delay->active_media_state = NULL;
+               SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_sip_session_get_name(session));
        case DELAYED_METHOD_UPDATE:
                res = 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, delay->media_state, 1);
+                       AST_SIP_SESSION_REFRESH_METHOD_UPDATE, delay->generate_new_sdp, delay->pending_media_state,
+                       delay->active_media_state, 1);
                /* Ownership of media state transitions to ast_sip_session_refresh */
-               delay->media_state = NULL;
-               return res;
+               delay->pending_media_state = NULL;
+               delay->active_media_state = NULL;
+               SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_sip_session_get_name(session));
        case DELAYED_METHOD_BYE:
                ast_sip_session_terminate(session, 0);
-               return 0;
+               SCOPE_EXIT_RTN_VALUE(0, "%s: Terminating session on delayed BYE\n", ast_sip_session_get_name(session));
        }
-       ast_log(LOG_WARNING, "Don't know how to send delayed %s(%d) request.\n",
+
+       SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Don't know how to send delayed %s(%d) request.\n",
+               ast_sip_session_get_name(session),
                delayed_method2str(delay->method), delay->method);
-       return -1;
 }
 
 /*!
@@ -1377,6 +1438,7 @@ static int invite_proceeding(void *vsession)
        struct ast_sip_session_delayed_request *delay;
        int found = 0;
        int res = 0;
+       SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session));
 
        AST_LIST_TRAVERSE_SAFE_BEGIN(&session->delayed_requests, delay, next) {
                switch (delay->method) {
@@ -1384,6 +1446,8 @@ static int invite_proceeding(void *vsession)
                        break;
                case DELAYED_METHOD_UPDATE:
                        AST_LIST_REMOVE_CURRENT(next);
+                       ast_trace(-1, "%s: Sending delayed %s request\n", ast_sip_session_get_name(session),
+                               delayed_method2str(delay->method));
                        res = send_delayed_request(session, delay);
                        delayed_request_free(delay);
                        if (!res) {
@@ -1402,7 +1466,7 @@ static int invite_proceeding(void *vsession)
        AST_LIST_TRAVERSE_SAFE_END;
 
        ao2_ref(session, -1);
-       return res;
+       SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_sip_session_get_name(session));
 }
 
 /*!
@@ -1422,6 +1486,7 @@ static int invite_terminated(void *vsession)
        int found = 0;
        int res = 0;
        int timer_running;
+       SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session));
 
        /* re-INVITE collision timer running? */
        timer_running = pj_timer_entry_running(&session->rescheduled_reinvite);
@@ -1440,6 +1505,8 @@ static int invite_terminated(void *vsession)
                }
                if (found) {
                        AST_LIST_REMOVE_CURRENT(next);
+                       ast_trace(-1, "%s: Sending delayed %s request\n", ast_sip_session_get_name(session),
+                               delayed_method2str(delay->method));
                        res = send_delayed_request(session, delay);
                        delayed_request_free(delay);
                        if (!res) {
@@ -1450,7 +1517,7 @@ static int invite_terminated(void *vsession)
        AST_LIST_TRAVERSE_SAFE_END;
 
        ao2_ref(session, -1);
-       return res;
+       SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_sip_session_get_name(session));
 }
 
 /*!
@@ -1467,6 +1534,7 @@ static int invite_collision_timeout(void *vsession)
 {
        struct ast_sip_session *session = vsession;
        int res;
+       SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session));
 
        if (session->inv_session->invite_tsx) {
                /*
@@ -1479,7 +1547,7 @@ static int invite_collision_timeout(void *vsession)
                res = invite_terminated(session);
        }
 
-       return res;
+       SCOPE_EXIT_RTN_VALUE(res, "%s\n", ast_sip_session_get_name(session));
 }
 
 /*!
@@ -1521,30 +1589,35 @@ static int delay_request(struct ast_sip_session *session,
        ast_sip_session_response_cb on_response,
        int generate_new_sdp,
        enum delayed_method method,
-       struct ast_sip_session_media_state *media_state)
+       struct ast_sip_session_media_state *pending_media_state,
+       struct ast_sip_session_media_state *active_media_state,
+       int queue_head)
 {
        struct ast_sip_session_delayed_request *delay = delayed_request_alloc(method,
-                       on_request, on_sdp_creation, on_response, generate_new_sdp, media_state);
+                       on_request, on_sdp_creation, on_response, generate_new_sdp, pending_media_state,
+                       active_media_state);
+       SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session));
 
        if (!delay) {
-               ast_sip_session_media_state_free(media_state);
-               return -1;
+               ast_sip_session_media_state_free(pending_media_state);
+               ast_sip_session_media_state_free(active_media_state);
+               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_ERROR, "Unable to allocate delay request\n");
        }
 
-       if (method == DELAYED_METHOD_BYE) {
+       if (method == DELAYED_METHOD_BYE || queue_head) {
                /* Send BYE as early as possible */
                AST_LIST_INSERT_HEAD(&session->delayed_requests, delay, next);
        } else {
                AST_LIST_INSERT_TAIL(&session->delayed_requests, delay, next);
        }
-       return 0;
+       SCOPE_EXIT_RTN_VALUE(0);
 }
 
 static pjmedia_sdp_session *generate_session_refresh_sdp(struct ast_sip_session *session)
 {
        pjsip_inv_session *inv_session = session->inv_session;
        const pjmedia_sdp_session *previous_sdp = NULL;
-       SCOPE_ENTER(1, "%s\n", ast_sip_session_get_name(session));
+       SCOPE_ENTER(3, "%s\n", ast_sip_session_get_name(session));
 
        if (inv_session->neg) {
                if (pjmedia_sdp_neg_was_answer_remote(inv_session->neg)) {
@@ -1617,7 +1690,8 @@ static void set_from_header(struct ast_sip_session *session)
        ast_channel_lock(session->channel);
        pjsip_from_domain = pbx_builtin_getvar_helper(session->channel, "SIPFROMDOMAIN");
        if (!ast_strlen_zero(pjsip_from_domain)) {
-               ast_debug(3, "From header domain reset by channel variable SIPFROMDOMAIN (%s)\n", pjsip_from_domain);
+               ast_debug(3, "%s: From header domain reset by channel variable SIPFROMDOMAIN (%s)\n",
+                       ast_sip_session_get_name(session), pjsip_from_domain);
                pj_strdup2(dlg_pool, &dlg_info_uri->host, pjsip_from_domain);
        }
        ast_channel_unlock(session->channel);
@@ -1649,57 +1723,521 @@ static void set_from_header(struct ast_sip_session *session)
     }
 }
 
+/*
+ * Helper macros for merging and validating media states
+ */
+#define STREAM_REMOVED(_stream) (ast_stream_get_state(_stream) == AST_STREAM_STATE_REMOVED)
+#define STATE_REMOVED(_stream_state) (_stream_state == AST_STREAM_STATE_REMOVED)
+#define STATE_NONE(_stream_state) (_stream_state == AST_STREAM_STATE_END)
+#define GET_STREAM_SAFE(_topology, _i) (_i < ast_stream_topology_get_count(_topology) ? ast_stream_topology_get_stream(_topology, _i) : NULL)
+#define GET_STREAM_STATE_SAFE(_stream) (_stream ? ast_stream_get_state(_stream) : AST_STREAM_STATE_END)
+#define GET_STREAM_NAME_SAFE(_stream) (_stream ? ast_stream_get_name(_stream) : "")
+
+/*!
+ * \internal
+ * \brief Validate a media state
+ *
+ * \param state Media state
+ *
+ * \retval 1 The media state is valid
+ * \retval 0 The media state is NOT valid
+ *
+ */
+static int is_media_state_valid(const char *session_name, struct ast_sip_session_media_state *state)
+{
+       int stream_count = ast_stream_topology_get_count(state->topology);
+       int session_count = AST_VECTOR_SIZE(&state->sessions);
+       int i;
+       int res = 0;
+       SCOPE_ENTER(3, "%s: Topology: %s\n", session_name,
+               ast_str_tmp(256, ast_stream_topology_to_str(state->topology, &STR_TMP)));
+
+       if (session_count != stream_count) {
+               SCOPE_EXIT_RTN_VALUE(0, "%s: %d media sessions but %d streams\n", session_name,
+                       session_count, stream_count);
+       }
+
+       for (i = 0; i < stream_count; i++) {
+               struct ast_sip_session_media *media = NULL;
+               struct ast_stream *stream = ast_stream_topology_get_stream(state->topology, i);
+               const char *stream_name = NULL;
+               int j;
+               SCOPE_ENTER(4, "%s: Checking stream %s\n", session_name, ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
+
+               if (!stream) {
+                       SCOPE_EXIT_EXPR(goto end, "%s: stream %d is null\n", session_name, i);
+               }
+               stream_name = ast_stream_get_name(stream);
+
+               for (j = 0; j < stream_count; j++) {
+                       struct ast_stream *possible_dup = ast_stream_topology_get_stream(state->topology, j);
+                       if (j == i || !possible_dup) {
+                               continue;
+                       }
+                       if (!STREAM_REMOVED(stream) && ast_strings_equal(stream_name, GET_STREAM_NAME_SAFE(possible_dup))) {
+                               SCOPE_EXIT_EXPR(goto end, "%s: stream %i %s is duplicated to %d\n", session_name,
+                                       i, stream_name, j);
+                       }
+               }
+
+               media = AST_VECTOR_GET(&state->sessions, i);
+               if (!media) {
+                       SCOPE_EXIT_EXPR(continue, "%s: media %d is null\n", session_name, i);
+               }
+
+               for (j = 0; j < session_count; j++) {
+                       struct ast_sip_session_media *possible_dup = AST_VECTOR_GET(&state->sessions, j);
+                       if (j == i || !possible_dup) {
+                               continue;
+                       }
+                       if (!ast_strlen_zero(media->label) && !ast_strlen_zero(possible_dup->label)
+                               && ast_strings_equal(media->label, possible_dup->label)) {
+                               SCOPE_EXIT_EXPR(goto end, "%s: media %d %s is duplicated to %d\n", session_name,
+                                       i, media->label, j);
+                       }
+               }
+
+               if (media->stream_num != i) {
+                       SCOPE_EXIT_EXPR(goto end, "%s: media %d has stream_num %d\n", session_name,
+                               i, media->stream_num);
+               }
+
+               if (media->type != ast_stream_get_type(stream)) {
+                       SCOPE_EXIT_EXPR(goto end, "%s: media %d has type %s but stream has type %s\n", stream_name,
+                               i, ast_codec_media_type2str(media->type), ast_codec_media_type2str(ast_stream_get_type(stream)));
+               }
+               SCOPE_EXIT("%s: Done with stream %s\n", session_name, ast_str_tmp(128, ast_stream_to_str(stream, &STR_TMP)));
+       }
+
+       res = 1;
+end:
+       SCOPE_EXIT_RTN_VALUE(res, "%s: %s\n", session_name, res ? "Valid" : "NOT Valid");
+}
+
+/*!
+ * \internal
+ * \brief Merge media states for a delayed session refresh
+ *
+ * \param session_name For log messages
+ * \param delayed_pending_state The pending media state at the time the resuest was queued
+ * \param delayed_active_state The active media state  at the time the resuest was queued
+ * \param current_active_state The current active media state
+ * \param run_validation Whether to run validation on the resulting media state or not
+ *
+ * \returns New merged topology or NULL if there's an error
+ *
+ */
+static struct ast_sip_session_media_state *resolve_refresh_media_states(
+       const char *session_name,
+       struct ast_sip_session_media_state *delayed_pending_state,
+       struct ast_sip_session_media_state *delayed_active_state,
+       struct ast_sip_session_media_state *current_active_state,
+       int run_post_validation)
+{
+       RAII_VAR(struct ast_sip_session_media_state *, new_pending_state, NULL, ast_sip_session_media_state_free);
+       struct ast_sip_session_media_state *returned_media_state = NULL;
+       struct ast_stream_topology *delayed_pending = delayed_pending_state->topology;
+       struct ast_stream_topology *delayed_active = delayed_active_state->topology;
+       struct ast_stream_topology *current_active = current_active_state->topology;
+       struct ast_stream_topology *new_pending = NULL;
+       int i;
+       int max_stream_count;
+       int res;
+       SCOPE_ENTER(2, "%s: DP: %s  DA: %s  CA: %s\n", session_name,
+               ast_str_tmp(256, ast_stream_topology_to_str(delayed_pending, &STR_TMP)),
+               ast_str_tmp(256, ast_stream_topology_to_str(delayed_active, &STR_TMP)),
+               ast_str_tmp(256, ast_stream_topology_to_str(current_active, &STR_TMP))
+       );
+
+       max_stream_count = MAX(ast_stream_topology_get_count(delayed_pending),
+               ast_stream_topology_get_count(delayed_active));
+       max_stream_count = MAX(max_stream_count, ast_stream_topology_get_count(current_active));
+
+       /*
+        * The new_pending_state is always based on the currently negotiated state because
+        * the stream ordering in its topology must be preserved.
+        */
+       new_pending_state = ast_sip_session_media_state_clone(current_active_state);
+       if (!new_pending_state) {
+               SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "%s: Couldn't clone current_active_state to new_pending_state\n", session_name);
+       }
+       new_pending = new_pending_state->topology;
+
+       for (i = 0; i < max_stream_count; i++) {
+               struct ast_stream *dp_stream = GET_STREAM_SAFE(delayed_pending, i);
+               struct ast_stream *da_stream = GET_STREAM_SAFE(delayed_active, i);
+               struct ast_stream *ca_stream = GET_STREAM_SAFE(current_active, i);
+               struct ast_stream *np_stream = GET_STREAM_SAFE(new_pending, i);
+               struct ast_stream *found_da_stream = NULL;
+               struct ast_stream *found_np_stream = NULL;
+               enum ast_stream_state dp_state = GET_STREAM_STATE_SAFE(dp_stream);
+               enum ast_stream_state da_state = GET_STREAM_STATE_SAFE(da_stream);
+               enum ast_stream_state ca_state = GET_STREAM_STATE_SAFE(ca_stream);
+               enum ast_stream_state np_state = GET_STREAM_STATE_SAFE(np_stream);
+               enum ast_stream_state found_da_state = AST_STREAM_STATE_END;
+               enum ast_stream_state found_np_state = AST_STREAM_STATE_END;
+               const char *da_name = GET_STREAM_NAME_SAFE(da_stream);
+               const char *dp_name = GET_STREAM_NAME_SAFE(dp_stream);
+               const char *ca_name = GET_STREAM_NAME_SAFE(ca_stream);
+               const char *np_name = GET_STREAM_NAME_SAFE(np_stream);
+               const char *found_da_name __attribute__((unused)) = "";
+               const char *found_np_name __attribute__((unused)) = "";
+               int found_da_slot __attribute__((unused)) = -1;
+               int found_np_slot = -1;
+               int removed_np_slot = -1;
+               int j;
+               SCOPE_ENTER(3, "%s: slot: %d DP: %s  DA: %s  CA: %s\n", session_name, i,
+                       ast_str_tmp(128, ast_stream_to_str(dp_stream, &STR_TMP)),
+                       ast_str_tmp(128, ast_stream_to_str(da_stream, &STR_TMP)),
+                       ast_str_tmp(128, ast_stream_to_str(ca_stream, &STR_TMP)));
+
+               if (STATE_NONE(da_state) && STATE_NONE(dp_state) && STATE_NONE(ca_state)) {
+                       SCOPE_EXIT_EXPR(break, "%s: All gone\n", session_name);
+               }
+
+               /*
+                * Simple cases are handled first to avoid having to search the NP and DA
+                * topologies for streams with the same name but not in the same position.
+                */
+
+               if (STATE_NONE(dp_state) && !STATE_NONE(da_state)) {
+                   /*
+                    * The slot in the delayed pending topology can't be empty if the delayed
+                    * active topology has a stream there.  Streams can't just go away.  They
+                    * can be reused or marked "removed" but they can't go away.
+                    */
+                   SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_WARNING, "%s: DP slot is empty but DA is not\n", session_name);
+               }
+
+               if (STATE_NONE(dp_state)) {
+                   /*
+                    * The current active topology can certainly have streams that weren't
+                    * in existence when the delayed request was queued.  In this case,
+                    * no action is needed since we already copied the current active topology
+                    * to the new pending one.
+                    */
+                   SCOPE_EXIT_EXPR(continue, "%s: No DP stream so use CA stream as is\n", session_name);
+               }
+
+               if (ast_strings_equal(dp_name, da_name) && ast_strings_equal(da_name, ca_name)) {
+                       /*
+                        * The delayed pending stream in this slot matches by name, the streams
+                        * in the same slot in the other two topologies.  Easy case.
+                        */
+                       ast_trace(-1, "%s: Same stream in all 3 states\n", session_name);
+                       if (dp_state == da_state && da_state == ca_state) {
+                               /* All the same state, no need to update. */
+                               SCOPE_EXIT_EXPR(continue, "%s: All in the same state so nothing to do\n", session_name);
+                       }
+                       if (da_state != ca_state) {
+                               /*
+                                * Something set the CA state between the time this request was queued
+                                * and now.  The CA state wins so we don't do anything.
+                                */
+                               SCOPE_EXIT_EXPR(continue, "%s: Ignoring request to change state from %s to %s\n",
+                                       session_name, ast_stream_state2str(ca_state), ast_stream_state2str(dp_state));
+                       }
+                       if (dp_state != da_state) {
+                               /* DP needs to update the state */
+                               ast_stream_set_state(np_stream, dp_state);
+                               SCOPE_EXIT_EXPR(continue, "%s: Changed NP stream state from %s to %s\n",
+                                       session_name, ast_stream_state2str(ca_state), ast_stream_state2str(dp_state));
+                       }
+               }
+
+               /*
+                * We're done with the simple cases.  For the rest, we need to identify if the
+                * DP stream we're trying to take action on is already in the other topologies
+                * possibly in a different slot.  To do that, if the stream in the DA or CA slots
+                * doesn't match the current DP stream, we need to iterate over the topology
+                * looking for a stream with the same name.
+                */
+
+               /*
+                * Since we already copied all of the CA streams to the NP topology, we'll use it
+                * instead of CA because we'll be updating the NP as we go.
+                */
+               if (!ast_strings_equal(dp_name, np_name)) {
+                       /*
+                        * The NP stream in this slot doesn't have the same name as the DP stream
+                        * so we need to see if it's in another NP slot.  We're not going to stop
+                        * when we find a matching stream because we also want to find the first
+                        * removed removed slot, if any, so we can re-use this slot.  We'll break
+                        * early if we find both before we reach the end.
+                        */
+                       ast_trace(-1, "%s: Checking if DP is already in NP somewhere\n", session_name);
+                       for (j = 0; j < ast_stream_topology_get_count(new_pending); j++) {
+                               struct ast_stream *possible_existing = ast_stream_topology_get_stream(new_pending, j);
+                               const char *possible_existing_name = GET_STREAM_NAME_SAFE(possible_existing);
+
+                               ast_trace(-1, "%s: Checking %s against %s\n", session_name, dp_name, possible_existing_name);
+                               if (found_np_slot == -1 && ast_strings_equal(dp_name, possible_existing_name)) {
+                                       ast_trace(-1, "%s: Pending stream %s slot %d is in NP slot %d\n", session_name,
+                                       dp_name, i, j);
+                                       found_np_slot = j;
+                                       found_np_stream = possible_existing;
+                                       found_np_state = ast_stream_get_state(possible_existing);
+                                       found_np_name = ast_stream_get_name(possible_existing);
+                               }
+                               if (STREAM_REMOVED(possible_existing) && removed_np_slot == -1) {
+                                       removed_np_slot = j;
+                               }
+                               if (removed_np_slot >= 0 && found_np_slot >= 0) {
+                                       break;
+                               }
+                       }
+               } else {
+                       /* Makes the subsequent code easier */
+                       found_np_slot = i;
+                       found_np_stream = np_stream;
+                       found_np_state = np_state;
+                       found_np_name = np_name;
+               }
+
+               if (!ast_strings_equal(dp_name, da_name)) {
+                       /*
+                        * The DA stream in this slot doesn't have the same name as the DP stream
+                        * so we need to see if it's in another DA slot.  In real life, the DA stream
+                        * in this slot could have a different name but there shouldn't be a case
+                        * where the DP stream is another slot in the DA topology.  Just in case though.
+                        * We don't care about removed slots in the DA topology.
+                        */
+                       ast_trace(-1, "%s: Checking if DP is already in DA somewhere\n", session_name);
+                       for (j = 0; j < ast_stream_topology_get_count(delayed_active); j++) {
+                               struct ast_stream *possible_existing = ast_stream_topology_get_stream(delayed_active, j);
+                               const char *possible_existing_name = GET_STREAM_NAME_SAFE(possible_existing);
+
+                               ast_trace(-1, "%s: Checking %s against %s\n", session_name, dp_name, possible_existing_name);
+                               if (ast_strings_equal(dp_name, possible_existing_name)) {
+                                       ast_trace(-1, "%s: Pending stream %s slot %d is already in delayed active slot %d\n",
+                                               session_name, dp_name, i, j);
+                                       found_da_slot = j;
+                                       found_da_stream = possible_existing;
+                                       found_da_state = ast_stream_get_state(possible_existing);
+                                       found_da_name = ast_stream_get_name(possible_existing);
+                                       break;
+                               }
+                       }
+               } else {
+                       /* Makes the subsequent code easier */
+                       found_da_slot = i;
+                       found_da_stream = da_stream;
+                       found_da_state = da_state;
+                       found_da_name = da_name;
+               }
+
+               ast_trace(-1, "%s: Found NP slot: %d  Found removed NP slot: %d Found DA slot: %d\n",
+                       session_name, found_np_slot, removed_np_slot, found_da_slot);
+
+               /*
+                * Now we know whether the DP stream is new or changing state and we know if the DP
+                * stream exists in the other topologies and if so, where in those topologies it exists.
+                */
+
+               if (!found_da_stream) {
+                       /*
+                        * The DP stream isn't in the DA topology which would imply that the intention of the
+                        * request was to add the stream, not change its state.  It's possible though that
+                        * the stream was added by another request between the time this request was queued
+                        * and now so we need to check the CA topology as well.
+                        */
+                       ast_trace(-1, "%s: There was no corresponding DA stream so the request was to add a stream\n", session_name);
+
+                       if (found_np_stream) {
+                               /*
+                                * We found it in the CA topology.  Since the intention was to add it
+                                * and it's already there, there's nothing to do.
+                                */
+                               SCOPE_EXIT_EXPR(continue, "%s: New stream requested but it's already in CA\n", session_name);
+                       } else {
+                               /* OK, it's not in either which would again imply that the intention of the
+                                * request was to add the stream.
+                                */
+                               ast_trace(-1, "%s: There was no corresponding NP stream\n", session_name);
+                               if (STATE_REMOVED(dp_state)) {
+                                       /*
+                                        * How can DP request to remove a stream that doesn't seem to exist anythere?
+                                        * It's not.  It's possible that the stream was already removed and the slot
+                                        * reused in the CA topology, but it would still have to exist in the DA
+                                        * topology.  Bail.
+                                        */
+                                       SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR,
+                                               "%s: Attempting to remove stream %d:%s but it doesn't exist anywhere.\n", session_name, i, dp_name);
+                               } else {
+                                       /*
+                                        * We're now sure we want to add the the stream.  Since we can re-use
+                                        * slots in the CA topology that have streams marked as "removed", we
+                                        * use the slot we saved in removed_np_slot if it exists.
+                                        */
+                                       ast_trace(-1, "%s: Checking for open slot\n", session_name);
+                                       if (removed_np_slot >= 0) {
+                                               struct ast_sip_session_media *old_media = AST_VECTOR_GET(&new_pending_state->sessions, removed_np_slot);
+                                               res = ast_stream_topology_set_stream(new_pending, removed_np_slot, ast_stream_clone(dp_stream, NULL));
+                                               if (res != 0) {
+                                                   SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_WARNING, "%s: Couldn't set stream in new topology\n", session_name);
+                                               }
+                                               /*
+                                                * Since we're reusing the removed_np_slot slot for something else, we need
+                                                * to free and remove any session media already in it.
+                                                * ast_stream_topology_set_stream() took care of freeing the old stream.
+                                                */
+                                               res = AST_VECTOR_REPLACE(&new_pending_state->sessions, removed_np_slot, NULL);
+                                               if (res != 0) {
+                                                   SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_WARNING, "%s: Couldn't replace media session\n", session_name);
+                                               }
+
+                                               ao2_cleanup(old_media);
+                                               SCOPE_EXIT_EXPR(continue, "%s: Replaced removed stream in slot %d\n",
+                                                       session_name, removed_np_slot);
+                                       } else {
+                                               int new_slot = ast_stream_topology_append_stream(new_pending, ast_stream_clone(dp_stream, NULL));
+                                               if (new_slot < 0) {
+                                                   SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_WARNING, "%s: Couldn't append stream in new topology\n", session_name);
+                                               }
+
+                                               res = AST_VECTOR_REPLACE(&new_pending_state->sessions, new_slot, NULL);
+                                               if (res != 0) {
+                                                   SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_WARNING, "%s: Couldn't replace media session\n", session_name);
+                                               }
+                                               SCOPE_EXIT_EXPR(continue, "%s: Appended new stream to slot %d\n",
+                                                       session_name, new_slot);
+                                       }
+                               }
+                       }
+               } else {
+                       /*
+                        * The DP stream exists in the DA topology so it's a change of some sort.
+                        */
+                       ast_trace(-1, "%s: There was a corresponding DA stream so the request was to change/remove a stream\n", session_name);
+                       if (dp_state == found_da_state) {
+                               /* No change? Let's see if it's in CA */
+                               if (!found_np_stream) {
+                                       /*
+                                        * The DP and DA state are the same which would imply that the stream
+                                        * already exists but it's not in the CA topology.  It's possible that
+                                        * between the time this request was queued and now the stream was removed
+                                        * from the CA topology and the slot used for something else.  Nothing
+                                        * we can do here.
+                                        */
+                                       SCOPE_EXIT_EXPR(continue, "%s: Stream doesn't exist in CA so nothing to do\n", session_name);
+                               } else if (dp_state == found_np_state) {
+                                       SCOPE_EXIT_EXPR(continue, "%s: States are the same all around so nothing to do\n", session_name);
+                               } else {
+                                       SCOPE_EXIT_EXPR(continue, "%s: Something changed the CA state so we're going to leave it as is\n", session_name);
+                               }
+                       } else {
+                               /* We have a state change. */
+                               ast_trace(-1, "%s: Requesting state change to %s\n", session_name, ast_stream_state2str(dp_state));
+                               if (!found_np_stream) {
+                                       SCOPE_EXIT_EXPR(continue, "%s: Stream doesn't exist in CA so nothing to do\n", session_name);
+                               } else if (da_state == found_np_state) {
+                                       ast_stream_set_state(found_np_stream, dp_state);
+                                       SCOPE_EXIT_EXPR(continue, "%s: Changed NP stream state from %s to %s\n",
+                                               session_name, ast_stream_state2str(found_np_state), ast_stream_state2str(dp_state));
+                               } else {
+                                       SCOPE_EXIT_EXPR(continue, "%s: Something changed the CA state so we're going to leave it as is\n",
+                                               session_name);
+                               }
+                       }
+               }
+
+               SCOPE_EXIT("%s: Done with slot %d\n", session_name, i);
+       }
+
+       ast_trace(-1, "%s: Resetting default media states\n", session_name);
+       for (i = 0; i < AST_MEDIA_TYPE_END; i++) {
+               int j;
+               new_pending_state->default_session[i] = NULL;
+               for (j = 0; j < AST_VECTOR_SIZE(&new_pending_state->sessions); j++) {
+                       struct ast_sip_session_media *media = AST_VECTOR_GET(&new_pending_state->sessions, j);
+                       struct ast_stream *stream = ast_stream_topology_get_stream(new_pending_state->topology, j);
+
+                       if (media && media->type == i && !STREAM_REMOVED(stream)) {
+                               new_pending_state->default_session[i] = media;
+                               break;
+                       }
+               }
+       }
+
+       if (run_post_validation) {
+               ast_trace(-1, "%s: Running post-validation\n", session_name);
+               if (!is_media_state_valid(session_name, new_pending_state)) {
+                       SCOPE_EXIT_LOG_RTN_VALUE(NULL, LOG_ERROR, "State not consistent\n");
+               }
+       }
+
+       /*
+        * We need to move the new pending state to another variable and set new_pending_state to NULL
+        * so RAII_VAR doesn't free it.
+        */
+       returned_media_state = new_pending_state;
+       new_pending_state = NULL;
+       SCOPE_EXIT_RTN_VALUE(returned_media_state, "%s: NP: %s\n", session_name,
+               ast_str_tmp(256, ast_stream_topology_to_str(new_pending, &STR_TMP)));
+}
+
 static int 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,
-               struct ast_sip_session_media_state *media_state,
+               struct ast_sip_session_media_state *pending_media_state,
+               struct ast_sip_session_media_state *active_media_state,
                int queued)
 {
        pjsip_inv_session *inv_session = session->inv_session;
        pjmedia_sdp_session *new_sdp = NULL;
        pjsip_tx_data *tdata;
+       int res;
+       SCOPE_ENTER(3, "%s: New SDP? %s  Queued? %s DP: %s  DA: %s\n", ast_sip_session_get_name(session),
+               generate_new_sdp ? "yes" : "no", queued ? "yes" : "no",
+               pending_media_state ? ast_str_tmp(256, ast_stream_topology_to_str(pending_media_state->topology, &STR_TMP)) : "none",
+               active_media_state ? ast_str_tmp(256, ast_stream_topology_to_str(active_media_state->topology, &STR_TMP)) : "none");
 
-       if (media_state && (!media_state->topology || !generate_new_sdp)) {
-               ast_sip_session_media_state_free(media_state);
-               return -1;
+       if (pending_media_state && (!pending_media_state->topology || !generate_new_sdp)) {
+
+               ast_sip_session_media_state_free(pending_media_state);
+               ast_sip_session_media_state_free(active_media_state);
+               SCOPE_EXIT_RTN_VALUE(-1, "%s: Not sending reinvite because %s%s\n", ast_sip_session_get_name(session),
+                       pending_media_state->topology == NULL ? "pending topology is null " : "",
+                               !generate_new_sdp ? "generate_new_sdp is false" : "");
        }
 
        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;
+               ast_sip_session_media_state_free(pending_media_state);
+               ast_sip_session_media_state_free(active_media_state);
+               SCOPE_EXIT_RTN_VALUE(0, "%s: Not sending reinvite because of disconnected state\n",
+                               ast_sip_session_get_name(session));
        }
 
        /* If the dialog has not yet been established we have to defer until it has */
        if (inv_session->dlg->state != PJSIP_DIALOG_STATE_ESTABLISHED) {
-               ast_debug(3, "Delay sending request to %s because dialog has not been established...\n",
-                       ast_sorcery_object_get_id(session->endpoint));
-               return delay_request(session, on_request_creation, on_sdp_creation, on_response,
+               res = 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,
-                       media_state);
+                       pending_media_state, active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued);
+               SCOPE_EXIT_RTN_VALUE(res, "%s: Delay sending reinvite because dialog has not been established\n",
+                       ast_sip_session_get_name(session));
        }
 
        if (method == AST_SIP_SESSION_REFRESH_METHOD_INVITE) {
                if (inv_session->invite_tsx) {
                        /* We can't send a reinvite yet, so delay it */
-                       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, media_state);
+                       res = delay_request(session, on_request_creation, on_sdp_creation,
+                               on_response, generate_new_sdp, DELAYED_METHOD_INVITE, pending_media_state,
+                               active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued);
+                       SCOPE_EXIT_RTN_VALUE(res, "%s: Delay sending reinvite because of outstanding transaction\n",
+                               ast_sip_session_get_name(session));
                } 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;
+                       ast_sip_session_media_state_free(pending_media_state);
+                       ast_sip_session_media_state_free(active_media_state);
+                       SCOPE_EXIT_RTN_VALUE(0, "%s: Not sending reinvite because not in confirmed state\n",
+                               ast_sip_session_get_name(session));
                }
        }
 
@@ -1708,19 +2246,22 @@ static int sip_session_refresh(struct ast_sip_session *session,
                if (inv_session->neg
                        && pjmedia_sdp_neg_get_state(inv_session->neg)
                                != PJMEDIA_SDP_NEG_STATE_DONE) {
-                       ast_debug(3, "Delay session refresh with new SDP to %s because SDP negotiation is not yet done...\n",
-                               ast_sorcery_object_get_id(session->endpoint));
-                       return delay_request(session, on_request_creation, on_sdp_creation,
+                       res = 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, media_state);
+                                       ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, pending_media_state,
+                               active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued);
+                       SCOPE_EXIT_RTN_VALUE(res, "%s: Delay session refresh with new SDP because SDP negotiation is not yet done\n",
+                               ast_sip_session_get_name(session));
                }
 
                /* If an explicitly requested media state has been provided use it instead of any pending one */
-               if (media_state) {
+               if (pending_media_state) {
                        int index;
                        int type_streams[AST_MEDIA_TYPE_END] = {0};
-                       struct ast_stream *stream;
+                       int topology_change_request = 0;
+
+                       ast_trace(-1, "%s: Pending media state exists\n", ast_sip_session_get_name(session));
 
                        /* Media state conveys a desired media state, so if there are outstanding
                         * delayed requests we need to ensure we go into the queue and not jump
@@ -1728,12 +2269,43 @@ static int sip_session_refresh(struct ast_sip_session *session,
                         * order.
                         */
                        if (!queued && !AST_LIST_EMPTY(&session->delayed_requests)) {
-                               ast_debug(3, "Delay sending reinvite to %s because of outstanding requests...\n",
-                                       ast_sorcery_object_get_id(session->endpoint));
-                               return delay_request(session, on_request_creation, on_sdp_creation,
+                               res = 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, media_state);
+                                               ? DELAYED_METHOD_INVITE : DELAYED_METHOD_UPDATE, pending_media_state,
+                                               active_media_state ? active_media_state : ast_sip_session_media_state_clone(session->active_media_state), queued);
+                               SCOPE_EXIT_RTN_VALUE(res, "%s: Delay sending reinvite because of outstanding requests\n",
+                                       ast_sip_session_get_name(session));
+                       }
+
+                       if (active_media_state) {
+                               struct ast_sip_session_media_state *new_pending_state;
+                               /*
+                                * We need to check if the passed in active and pending states are equal
+                                * before we run the media states resolver.  We'll use the flag later
+                                * to signal whether this was topology change or some other change such
+                                * as a connected line change.
+                                */
+                               topology_change_request = !ast_stream_topology_equal(active_media_state->topology, pending_media_state->topology);
+
+                               ast_trace(-1, "%s: Active media state exists and is%s equal to pending\n", ast_sip_session_get_name(session),
+                                       topology_change_request ? " not" : "");
+                               ast_trace(-1, "%s: DP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(pending_media_state->topology, &STR_TMP)));
+                               ast_trace(-1, "%s: DA: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(active_media_state->topology, &STR_TMP)));
+                               ast_trace(-1, "%s: CP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(session->pending_media_state->topology, &STR_TMP)));
+                               ast_trace(-1, "%s: CA: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP)));
+
+                               new_pending_state = resolve_refresh_media_states(ast_sip_session_get_name(session),
+                                       pending_media_state, active_media_state, session->active_media_state, 1);
+                               if (new_pending_state) {
+                                       ast_trace(-1, "%s: NP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(new_pending_state->topology, &STR_TMP)));
+                                       ast_sip_session_media_state_free(pending_media_state);
+                                       pending_media_state = new_pending_state;
+                               } else {
+                                       ast_sip_session_media_state_reset(pending_media_state);
+                                       ast_sip_session_media_state_free(active_media_state);
+                                       SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Unable to merge media states\n", ast_sip_session_get_name(session));
+                               }
                        }
 
                        /* Prune the media state so the number of streams fit within the configured limits - we do it here
@@ -1741,39 +2313,49 @@ static int sip_session_refresh(struct ast_sip_session *session,
                         * 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) {
-                               struct ast_stream *existing_stream = NULL;
+                       ast_trace(-1, "%s: Pruning and checking formats of streams\n", ast_sip_session_get_name(session));
 
-                               stream = ast_stream_topology_get_stream(media_state->topology, index);
+                       for (index = 0; index < ast_stream_topology_get_count(pending_media_state->topology); ++index) {
+                               struct ast_stream *existing_stream = NULL;
+                               struct ast_stream *stream = ast_stream_topology_get_stream(pending_media_state->topology, index);
+                               SCOPE_ENTER(4, "%s: Checking stream %s\n", ast_sip_session_get_name(session),
+                                       ast_stream_get_name(stream));
 
                                if (session->active_media_state->topology &&
                                        index < ast_stream_topology_get_count(session->active_media_state->topology)) {
                                        existing_stream = ast_stream_topology_get_stream(session->active_media_state->topology, index);
+                                       ast_trace(-1, "%s: Found existing stream %s\n", ast_sip_session_get_name(session),
+                                               ast_stream_get_name(existing_stream));
                                }
 
                                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);
+                                       if (index < AST_VECTOR_SIZE(&pending_media_state->sessions)) {
+                                               struct ast_sip_session_media *session_media = AST_VECTOR_GET(&pending_media_state->sessions, index);
 
                                                ao2_cleanup(session_media);
-                                               AST_VECTOR_REMOVE(&media_state->sessions, index, 1);
+                                               AST_VECTOR_REMOVE(&pending_media_state->sessions, index, 1);
                                        }
 
-                                       ast_stream_topology_del_stream(media_state->topology, index);
+                                       ast_stream_topology_del_stream(pending_media_state->topology, index);
+                                       ast_trace(-1, "%s: Dropped overlimit stream %s\n", ast_sip_session_get_name(session),
+                                               ast_stream_get_name(stream));
 
                                        /* A stream has potentially moved into our spot so we need to jump back so we process it */
                                        index -= 1;
-                                       continue;
+                                       SCOPE_EXIT_EXPR(continue);
                                }
 
                                /* No need to do anything with stream if it's media state is removed */
                                if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {
                                        /* If there is no existing stream we can just not have this stream in the topology at all. */
                                        if (!existing_stream) {
-                                               ast_stream_topology_del_stream(media_state->topology, index);
+                                               ast_trace(-1, "%s: Dropped removed stream %s\n", ast_sip_session_get_name(session),
+                                                       ast_stream_get_name(stream));
+                                               ast_stream_topology_del_stream(pending_media_state->topology, index);
+                                               /* TODO: Do we need to remove the corresponding media state? */
                                                index -= 1;
                                        }
-                                       continue;
+                                       SCOPE_EXIT_EXPR(continue);
                                }
 
                                /* Enforce the configured allowed codecs on audio and video streams */
@@ -1783,8 +2365,10 @@ static int sip_session_refresh(struct ast_sip_session *session,
 
                                        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_sip_session_media_state_free(pending_media_state);
+                                               ast_sip_session_media_state_free(active_media_state);
+                                               res = -1;
+                                               SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Unable to alloc format caps\n", ast_sip_session_get_name(session));
                                        }
                                        ast_format_cap_get_compatible(ast_stream_get_formats(stream), session->endpoint->media.codecs, joint_cap);
                                        if (!ast_format_cap_count(joint_cap)) {
@@ -1794,9 +2378,10 @@ static int sip_session_refresh(struct ast_sip_session *session,
                                                        /* If there is no existing stream we can just not have this stream in the topology
                                                         * at all.
                                                         */
-                                                       ast_stream_topology_del_stream(media_state->topology, index);
+                                                       ast_stream_topology_del_stream(pending_media_state->topology, index);
                                                        index -= 1;
-                                                       continue;
+                                                       SCOPE_EXIT_EXPR(continue, "%s: Dropped incompatible stream %s\n",
+                                                               ast_sip_session_get_name(session), ast_stream_get_name(stream));
                                                } else if (ast_stream_get_state(stream) != ast_stream_get_state(existing_stream) ||
                                                                strcmp(ast_stream_get_name(stream), ast_stream_get_name(existing_stream))) {
                                                        /* If the underlying stream is a different type or different name then we have to
@@ -1804,7 +2389,8 @@ static int sip_session_refresh(struct ast_sip_session *session,
                                                         * is preserved.
                                                         */
                                                        ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);
-                                                       continue;
+                                                       SCOPE_EXIT_EXPR(continue, "%s: Dropped incompatible stream %s\n",
+                                                               ast_sip_session_get_name(session), ast_stream_get_name(stream));
                                                } else {
                                                        /* However if the stream is otherwise remaining the same we can keep the formats
                                                         * that exist on it already which allows media to continue to flow. We don't modify
@@ -1819,6 +2405,8 @@ static int sip_session_refresh(struct ast_sip_session *session,
                                }
 
                                ++type_streams[ast_stream_get_type(stream)];
+
+                               SCOPE_EXIT();
                        }
 
                        if (session->active_media_state->topology) {
@@ -1827,84 +2415,108 @@ static int sip_session_refresh(struct ast_sip_session *session,
                                 * 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);
+
+                               for (index = ast_stream_topology_get_count(pending_media_state->topology);
                                        index < ast_stream_topology_get_count(session->active_media_state->topology); ++index) {
+                                       struct ast_stream *stream = ast_stream_topology_get_stream(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);
+                                       int position;
+                                       SCOPE_ENTER(4, "%s: Stream %s not in pending\n", ast_sip_session_get_name(session),
+                                               ast_stream_get_name(stream));
 
                                        cloned = ast_stream_clone(stream, NULL);
                                        if (!cloned) {
-                                               ast_sip_session_media_state_free(media_state);
-                                               return -1;
+                                               ast_sip_session_media_state_free(pending_media_state);
+                                               ast_sip_session_media_state_free(active_media_state);
+                                               res = -1;
+                                               SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Unable to clone stream %s\n",
+                                                       ast_sip_session_get_name(session), ast_stream_get_name(stream));
                                        }
 
                                        ast_stream_set_state(cloned, AST_STREAM_STATE_REMOVED);
-                                       if (ast_stream_topology_append_stream(media_state->topology, cloned) < 0) {
+                                       position = ast_stream_topology_append_stream(pending_media_state->topology, cloned);
+                                       if (position < 0) {
                                                ast_stream_free(cloned);
-                                               ast_sip_session_media_state_free(media_state);
-                                               return -1;
+                                               ast_sip_session_media_state_free(pending_media_state);
+                                               ast_sip_session_media_state_free(active_media_state);
+                                               res = -1;
+                                               SCOPE_EXIT_LOG_EXPR(goto end, LOG_ERROR, "%s: Unable to append cloned stream\n",
+                                                       ast_sip_session_get_name(session));
                                        }
+                                       SCOPE_EXIT("%s: Appended empty stream in position %d to make counts match\n",
+                                               ast_sip_session_get_name(session), position);
                                }
 
-                               /* 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);
+                               /*
+                                * We can suppress this re-invite if the pending topology is equal to the currently
+                                * active topology but only if this re-invite was the result of a requested topology
+                                * change.  If it was the result of some other change, like connected line, then
+                                * we don't want to suppress it even though the topologies are equal.
+                                */
+                               if (topology_change_request && ast_stream_topology_equal(session->active_media_state->topology, pending_media_state->topology)) {
+                                       ast_trace(-1, "%s: CA: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(session->active_media_state->topology, &STR_TMP)));
+                                       ast_trace(-1, "%s: NP: %s\n", ast_sip_session_get_name(session), ast_str_tmp(256, ast_stream_topology_to_str(pending_media_state->topology, &STR_TMP)));
+                                       ast_sip_session_media_state_free(pending_media_state);
+                                       ast_sip_session_media_state_free(active_media_state);
                                        /* For external consumers we return 0 to say success, but internally for
                                         * send_delayed_request we return a separate value to indicate that this
                                         * session refresh would be redundant so we didn't send it
                                         */
-                                       return queued ? 1 : 0;
+                                       SCOPE_EXIT_RTN_VALUE(queued ? 1 : 0, "%s: Topologies are equal. Not sending re-invite\n",
+                                               ast_sip_session_get_name(session));
                                }
                        }
 
                        ast_sip_session_media_state_free(session->pending_media_state);
-                       session->pending_media_state = media_state;
+                       session->pending_media_state = pending_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;
+                       ast_sip_session_media_state_free(active_media_state);
+                       SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Failed to generate session refresh SDP. Not sending session refresh\n",
+                               ast_sip_session_get_name(session));
                }
                if (on_sdp_creation) {
                        if (on_sdp_creation(session, new_sdp)) {
                                ast_sip_session_media_state_reset(session->pending_media_state);
-                               return -1;
+                               ast_sip_session_media_state_free(active_media_state);
+                               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: on_sdp_creation failed\n", ast_sip_session_get_name(session));
                        }
                }
        }
 
        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;
+                       ast_sip_session_media_state_free(active_media_state);
+                       SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Failed to create reinvite properly\n", ast_sip_session_get_name(session));
                }
        } 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;
+               ast_sip_session_media_state_free(active_media_state);
+               SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: Failed to create UPDATE properly\n", ast_sip_session_get_name(session));
        }
        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;
+                       ast_sip_session_media_state_free(active_media_state);
+                       SCOPE_EXIT_LOG_RTN_VALUE(-1, LOG_WARNING, "%s: on_request_creation failed.\n", ast_sip_session_get_name(session));
                }
        }
-       ast_debug(3, "Sending session refresh SDP via %s to %s\n",
-               method == AST_SIP_SESSION_REFRESH_METHOD_INVITE ? "re-INVITE" : "UPDATE",
-               ast_sorcery_object_get_id(session->endpoint));
        ast_sip_session_send_request_with_cb(session, tdata, on_response);
-       return 0;
+       ast_sip_session_media_state_free(active_media_state);
+
+end:
+       SCOPE_EXIT_RTN_VALUE(res, "%s: Sending session refresh SDP via %s\n", ast_sip_session_get_name(session),
+               method == AST_SIP_SESSION_REFRESH_METHOD_INVITE ? "re-INVITE" : "UPDATE");
 }
 
 int ast_sip_session_refresh(struct ast_sip_session *session,
@@ -1915,7 +2527,7 @@ int ast_sip_session_refresh(struct ast_sip_session *session,
                struct ast_sip_session_media_state *media_state)
 {
        return sip_session_refresh(session, on_request_creation, on_sdp_creation,
-               on_response, method, generate_new_sdp, media_state, 0);
+               on_response, method, generate_new_sdp, media_state, NULL, 0);
 }
 
 int ast_sip_session_regenerate_answer(struct ast_sip_session *session,
@@ -2082,7 +2694,7 @@ static int sdp_requires_deferral(struct ast_sip_session *session, const pjmedia_
 
                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);
+                       ast_debug(3, "%s: No registered SDP handlers for media type '%s'\n", ast_sip_session_get_name(session), media);
                        continue;
                }
                AST_LIST_TRAVERSE(&handler_list->list, handler, next) {
@@ -2130,6 +2742,11 @@ static pj_bool_t session_reinvite_on_rx_request(pjsip_rx_data *rdata)
                return PJ_FALSE;
        }
 
+       if (session->inv_session->invite_tsx) {
+               /* There's a transaction in progress so bail now and let pjproject send 491 */
+               return PJ_FALSE;
+       }
+
        if (session->deferred_reinvite) {
                pj_str_t key, deferred_key;
                pjsip_tx_data *tdata;
@@ -2194,11 +2811,13 @@ static pj_bool_t session_reinvite_on_rx_request(pjsip_rx_data *rdata)
                        pjmedia_sdp_media *m = local->media[i];
                        pjmedia_sdp_attr *recvonly;
                        pjmedia_sdp_attr *inactive;
+                       pjmedia_sdp_attr *sendonly;
 
                        recvonly = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "recvonly", NULL);
                        inactive = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "inactive", NULL);
-                       if (recvonly || inactive) {
-                               pjmedia_sdp_attr *to_remove = recvonly ?: inactive;
+                       sendonly = pjmedia_sdp_attr_find2(m->attr_count, m->attr, "sendonly", NULL);
+                       if (recvonly || inactive || sendonly) {
+                               pjmedia_sdp_attr *to_remove = recvonly ?: inactive ?: sendonly;
                                pjmedia_sdp_attr *sendrecv;
 
                                pjmedia_sdp_attr_remove(&m->attr_count, m->attr, to_remove);
@@ -2332,11 +2951,13 @@ static void session_destructor(void *obj)
        struct ast_sip_session *session = obj;
        struct ast_sip_session_delayed_request *delay;
 
+#ifdef TEST_FRAMEWORK
        /* We dup the endpoint ID in case the endpoint gets freed out from under us */
        const char *endpoint_name = session->endpoint ?
                ast_strdupa(ast_sorcery_object_get_id(session->endpoint)) : "<none>";
+#endif
 
-       ast_debug(3, "Destroying SIP session with endpoint %s\n", endpoint_name);
+       ast_debug(3, "%s: Destroying SIP session\n", ast_sip_session_get_name(session));
 
        ast_test_suite_event_notify("SESSION_DESTROYING",
                "Endpoint: %s\r\n"
@@ -2653,18 +3274,19 @@ static pj_bool_t outbound_invite_auth(pjsip_rx_data *rdata)
        }
 
        inv = pjsip_dlg_get_inv_session(dlg);
+       session = inv->mod_data[session_module.id];
+
        if (PJSIP_INV_STATE_CONFIRMED <= inv->state) {
                /*
                 * We cannot handle reINVITE authentication at this
                 * time because the reINVITE transaction is still in
                 * progress.
                 */
-               ast_debug(1, "A reINVITE is being challenged.\n");
+               ast_debug(3, "%s: A reINVITE is being challenged\n", ast_sip_session_get_name(session));
                return PJ_FALSE;
        }
-       ast_debug(1, "Initial INVITE is being challenged.\n");
+       ast_debug(3, "%s: Initial INVITE is being challenged.\n", ast_sip_session_get_name(session));
 
-       session = inv->mod_data[session_module.id];
 
        if (ast_sip_create_request_with_auth(&session->endpoint->outbound_auths, rdata,
                tsx->last_tx, &tdata)) {
@@ -2891,12 +3513,12 @@ void ast_sip_session_terminate(struct ast_sip_session *session, int response)
                break;
        case PJSIP_INV_STATE_CONFIRMED:
                if (session->inv_session->invite_tsx) {