From 86f1bce18679bc6d0cd389181295e04fa67adf1f Mon Sep 17 00:00:00 2001 From: George Joseph <gjoseph@digium.com> Date: Thu, 20 Aug 2020 10:21:18 -0600 Subject: [PATCH] res_pjsip_session: Handle multi-stream re-invites better When both Asterisk and a UA send re-invites at the same time, both send 491 "Transaction in progress" responses to each other and back off a specified amount of time before retrying. When Asterisk prepares to send its re-invite, it sets up the session's pending media state with the new topology it wants, then sends the re-invite. Unfortunately, when it received the re-invite from the UA, it partially processed the media in the re-invite and reset the pending media state before sending the 491 losing the state it set in its own re-invite. Asterisk also was not tracking re-invites received while an existing re-invite was queued resulting in sending stale SDP with missing or duplicated streams, or no re-invite at all because we erroneously determined that a re-invite wasn't needed. There was also an issue in bridge_softmix where we were using a stream from the wrong topology to determine if a stream was added. This also caused us to erroneously determine that a re-invite wasn't needed. Regardless of how the delayed re-invite was triggered, we need to reconcile the topology that was active at the time the delayed request was queued, the pending topology of the queued request, and the topology currently active on the session. To do this we need a topology resolver AND we need to make stream named unique so we can accurately tell what a stream has been added or removed and if we can re-use a slot in the topology. Summary of changes: * bridge_softmix: * We no longer reset the stream name to "removed" in remove_all_original_streams(). That was causing multiple streams to have the same name and wrecked the checks for duplicate streams. * softmix_bridge_stream_sources_update() was checking the old_stream to see if it had the softmix prefix and not considering the stream as "new" if it did. If the stream in that slot has something in it because another re-invite happened, then that slot in old might have a softmix stream but the same stream in new might actually be a new one. Now we check the new_stream's name instead of the old_stream's. * stream: * Instead of using plain media type name ("audio", "video", etc) as the default stream name, we now append the stream position to it to make it unique. We need to do this so we can distinguish multiple streams of the same type from each other. * When we set a stream's state to REMOVED, we no longer reset its name to "removed" or destroy its metadata. Again, we need to do this so we can distinguish multiple streams of the same type from each other. * res_pjsip_session: * Added resolve_refresh_media_states() that takes in 3 media states and creates an up-to-date pending media state that includes the changes that might have happened while a delayed session refresh was in the delayed queue. * Added is_media_state_valid() that checks the consistency of a media state and returns a true/false value. A valid state has: * The same number of stream entries as media session entries. Some media session entries can be NULL however. * No duplicate streams. * A valid stream for each non-NULL media session. * A stream that matches each media session's stream_num and media type. * Updated handle_incoming_sdp() to set the stream name to include the stream position number in the name to make it unique. * Updated the ast_sip_session_delayed_request structure to include both the pending and active media states and updated the associated delay functions to process them. * Updated sip_session_refresh() to accept both the pending and active media states that were in effect when the request was originally queued and to pass them on should the request need to be delayed again. * Updated sip_session_refresh() to call resolve_refresh_media_states() and substitute its results for the pending state passed in. * Updated sip_session_refresh() with additional debugging. * Updated session_reinvite_on_rx_request() to simply return PJ_FALSE to pjproject if a transaction is in progress. This stops us from creating a partial pending media state that would be invalid later on. * Updated reschedule_reinvite() to clone both the current pending and active media states and pass them to delay_request() so the resolver can tell what the original intention of the re-invite was. * Added a large unit test for the resolver. ASTERISK-29014 Change-Id: Id3440972943c611a15f652c6c569fa0e4536bfcb --- bridges/bridge_softmix.c | 8 +- include/asterisk/stream.h | 4 + main/stream.c | 30 +- res/res_pjsip_session.c | 1331 ++++++++++++++++++++++++++++++++++--- 4 files changed, 1252 insertions(+), 121 deletions(-) diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c index 817f8b2a91..931cf62dfc 100644 --- a/bridges/bridge_softmix.c +++ b/bridges/bridge_softmix.c @@ -1081,11 +1081,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; } @@ -2272,7 +2268,7 @@ static void softmix_bridge_stream_sources_update(struct ast_bridge *bridge, stru /* 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; } diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h index 205feeb9c4..387c5b378e 100644 --- a/include/asterisk/stream.h +++ b/include/asterisk/stream.h @@ -718,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); @@ -775,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); diff --git a/main/stream.c b/main/stream.c index 859018d5ff..e464c5e8d8 100644 --- a/main/stream.c +++ b/main/stream.c @@ -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; @@ -381,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) @@ -765,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; } @@ -821,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); } @@ -879,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); @@ -894,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; diff --git a/res/res_pjsip_session.c b/res/res_pjsip_session.c index f07ee38adb..0385a96a8d 100644 --- a/res/res_pjsip_session.c +++ b/res/res_pjsip_session.c @@ -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 */ @@ -493,7 +494,10 @@ struct ast_sip_session_media *ast_sip_session_media_state_add(struct ast_sip_ses * 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) { + return session_media; + } } /* Determine if we can reuse the session media from the active media state if present */ @@ -777,27 +781,38 @@ static int handle_incoming_sdp(struct ast_sip_session *session, const pjmedia_sd } 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); + + 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"); + } } - stream = ast_stream_alloc(existing_stream ? ast_stream_get_name(existing_stream) : ast_codec_media_type2str(type), type); + if (ast_strlen_zero(stream_name)) { + if (ast_asprintf(&stream_name, "%s-%d", ast_codec_media_type2str(type), i) < 0) { + return -1; + } + } + + stream = ast_stream_alloc(stream_name, type); if (!stream) { SCOPE_EXIT_RTN_VALUE(-1, "Couldn't create stream\n"); } + + 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); SCOPE_EXIT_RTN_VALUE(-1, "Couldn't set stream\n"); } - 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); - } - } /* For backwards compatibility with the core the default audio stream is always sendrecv */ if (!ast_sip_session_is_pending_stream_default(session, stream) || strcmp(media, "audio")) { @@ -1288,7 +1303,9 @@ 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 +1315,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 +1328,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); } @@ -1341,16 +1361,20 @@ static int send_delayed_request(struct ast_sip_session *session, struct ast_sip_ 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; + delay->pending_media_state = NULL; + delay->active_media_state = NULL; return res; 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; + delay->pending_media_state = NULL; + delay->active_media_state = NULL; return res; case DELAYED_METHOD_BYE: ast_sip_session_terminate(session, 0); @@ -1521,17 +1545,21 @@ 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); if (!delay) { - ast_sip_session_media_state_free(media_state); + ast_sip_session_media_state_free(pending_media_state); + ast_sip_session_media_state_free(active_media_state); return -1; } - 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 { @@ -1649,57 +1677,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 +2200,21 @@ 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; + + 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 +2222,35 @@ 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; + + ast_trace(-1, "%s: Active media state exists\n", ast_sip_session_get_name(session)); + 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 +2258,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 +2310,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 +2323,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 +2334,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 +2350,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 +2360,103 @@ 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); + if (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,9 +2467,8 @@ 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, ast_sip_session_sdp_creation_cb on_sdp_creation) { @@ -2130,6 +2681,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; @@ -2898,7 +3454,7 @@ void ast_sip_session_terminate(struct ast_sip_session *session, int response) /* If this is delayed the only thing that will happen is a BYE request so we don't * actually need to store the response code for when it happens. */ - delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE, NULL); + delay_request(session, NULL, NULL, NULL, 0, DELAYED_METHOD_BYE, NULL, NULL, 1); break; } /* Fall through */ @@ -3570,13 +4126,32 @@ static void reschedule_reinvite(struct ast_sip_session *session, ast_sip_session { pjsip_inv_session *inv = session->inv_session; pj_time_val tv; + struct ast_sip_session_media_state *pending_media_state; + struct ast_sip_session_media_state *active_media_state; + const char *session_name = ast_sip_session_get_name(session); - ast_debug(3, "Endpoint '%s(%s)' re-INVITE collision.\n", - ast_sorcery_object_get_id(session->endpoint), - session->channel ? ast_channel_name(session->channel) : ""); - if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE, NULL)) { + ast_debug(3, "%s re-INVITE collision.\n", session_name); + + pending_media_state = ast_sip_session_media_state_clone(session->pending_media_state); + if (!pending_media_state) { + ast_log(LOG_ERROR, "%s: Failed to clone pending media state\n", session_name); return; } + + active_media_state = ast_sip_session_media_state_clone(session->active_media_state); + if (!active_media_state) { + ast_sip_session_media_state_free(pending_media_state); + ast_log(LOG_ERROR, "%s: Failed to clone active media state\n", session_name); + return; + } + + if (delay_request(session, NULL, NULL, on_response, 1, DELAYED_METHOD_INVITE, pending_media_state, + active_media_state, 1)) { + ast_sip_session_media_state_free(pending_media_state); + ast_sip_session_media_state_free(active_media_state); + ast_log(LOG_ERROR, "%s: Failed to add delayed request\n", session_name); + } + if (pj_timer_entry_running(&session->rescheduled_reinvite)) { /* Timer already running. Something weird is going on. */ ast_debug(1, "Endpoint '%s(%s)' re-INVITE collision while timer running!!!\n", @@ -4709,6 +5284,557 @@ static void session_outgoing_nat_hook(pjsip_tx_data *tdata, struct ast_sip_trans ast_sip_mod_data_set(tdata->pool, tdata->mod_data, session_module.id, MOD_DATA_NAT_HOOK, nat_hook); } +#ifdef TEST_FRAMEWORK + +static struct ast_stream *test_stream_alloc(const char *name, enum ast_media_type type, enum ast_stream_state state) +{ + struct ast_stream *stream; + + stream = ast_stream_alloc(name, type); + if (!stream) { + return NULL; + } + ast_stream_set_state(stream, state); + + return stream; +} + +static struct ast_sip_session_media *test_media_add( + struct ast_sip_session_media_state *media_state, const char *name, enum ast_media_type type, + enum ast_stream_state state, int position) +{ + struct ast_sip_session_media *session_media = NULL; + struct ast_stream *stream = NULL; + + stream = test_stream_alloc(name, type, state); + if (!stream) { + return NULL; + } + + if (position >= 0 && position < ast_stream_topology_get_count(media_state->topology)) { + ast_stream_topology_set_stream(media_state->topology, position, stream); + } else { + position = ast_stream_topology_append_stream(media_state->topology, stream); + } + + session_media = ao2_alloc_options(sizeof(*session_media), session_media_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); + if (!session_media) { + return NULL; + } + + session_media->keepalive_sched_id = -1; + session_media->timeout_sched_id = -1; + session_media->type = type; + session_media->stream_num = position; + session_media->bundle_group = -1; + strcpy(session_media->label, name); + + if (AST_VECTOR_REPLACE(&media_state->sessions, position, session_media)) { + ao2_ref(session_media, -1); + + return NULL; + } + + /* If this stream will be active in some way and it is the first of this type then consider this the default media session to match */ + if (!media_state->default_session[type] && ast_stream_get_state(ast_stream_topology_get_stream(media_state->topology, position)) != AST_STREAM_STATE_REMOVED) { + media_state->default_session[type] = session_media; + } + + return session_media; +} + +static int test_is_media_session_equal(struct ast_sip_session_media *left, struct ast_sip_session_media *right) +{ + if (left == right) { + return 1; + } + + if (!left) { + return 1; + } + + if (!right) { + return 0; + } + return memcmp(left, right, sizeof(*left)) == 0; +} + +static int test_is_media_state_equal(struct ast_sip_session_media_state *left, struct ast_sip_session_media_state *right, + int assert_on_failure) +{ + int i; + SCOPE_ENTER(2); + + if (left == right) { + SCOPE_EXIT_RTN_VALUE(1, "equal\n"); + } + + if (!(left && right)) { + ast_assert(!assert_on_failure); + SCOPE_EXIT_RTN_VALUE(0, "one is null: left: %p right: %p\n", left, right); + } + + if (!ast_stream_topology_equal(left->topology, right->topology)) { + ast_assert(!assert_on_failure); + SCOPE_EXIT_RTN_VALUE(0, "topologies differ\n"); + } + if (AST_VECTOR_SIZE(&left->sessions) != AST_VECTOR_SIZE(&right->sessions)) { + ast_assert(!assert_on_failure); + SCOPE_EXIT_RTN_VALUE(0, "session vector sizes different: left %lu != right %lu\n", AST_VECTOR_SIZE(&left->sessions), + AST_VECTOR_SIZE(&right->sessions)); + } + if (AST_VECTOR_SIZE(&left->read_callbacks) != AST_VECTOR_SIZE(&right->read_callbacks)) { + ast_assert(!assert_on_failure); + SCOPE_EXIT_RTN_VALUE(0, "read_callback vector sizes different: left %lu != right %lu\n", AST_VECTOR_SIZE(&left->read_callbacks), + AST_VECTOR_SIZE(&right->read_callbacks)); + } + + for (i = 0; i < AST_VECTOR_SIZE(&left->sessions) ; i++) { + if (!test_is_media_session_equal(AST_VECTOR_GET(&left->sessions, i), AST_VECTOR_GET(&right->sessions, i))) { + ast_assert(!assert_on_failure); + SCOPE_EXIT_RTN_VALUE(0, "Media session %d different\n", i); + } + } + + for (i = 0; i < AST_VECTOR_SIZE(&left->read_callbacks) ; i++) { + if (memcmp(AST_VECTOR_GET_ADDR(&left->read_callbacks, i), + AST_VECTOR_GET_ADDR(&right->read_callbacks, i), + sizeof(struct ast_sip_session_media_read_callback_state)) != 0) { + ast_assert(!assert_on_failure); + SCOPE_EXIT_RTN_VALUE(0, "read_callback %d different\n", i); + } + } + + for (i = 0; i < AST_MEDIA_TYPE_END; i++) { + if (!(left->default_session[i] && right->default_session[i])) { + continue; + } + if (!left->default_session[i] || !right->default_session[i] + || left->default_session[i]->stream_num != right->default_session[i]->stream_num) { + ast_assert(!assert_on_failure); + SCOPE_EXIT_RTN_VALUE(0, "Default media session %d different. Left: %s Right: %s\n", i, + left->default_session[i] ? left->default_session[i]->label : "null", + right->default_session[i] ? right->default_session[i]->label : "null"); + } + } + + SCOPE_EXIT_RTN_VALUE(1, "equal\n"); +} + +AST_TEST_DEFINE(test_resolve_refresh_media_states) +{ +#define FREE_STATE() \ +({ \ + ast_sip_session_media_state_free(new_pending_state); \ + new_pending_state = NULL; \ + ast_sip_session_media_state_free(delayed_pending_state); \ + delayed_pending_state = NULL; \ + ast_sip_session_media_state_free(delayed_active_state); \ + delayed_active_state = NULL; \ + ast_sip_session_media_state_free(current_active_state); \ + current_active_state = NULL; \ + ast_sip_session_media_state_free(expected_pending_state); \ + expected_pending_state = NULL; \ +}) + +#define RESET_STATE(__num) \ +({ \ + testnum=__num; \ + ast_trace(-1, "Test %d\n", testnum); \ + test_failed = 0; \ + delayed_pending_state = ast_sip_session_media_state_alloc(); \ + delayed_pending_state->topology = ast_stream_topology_alloc(); \ + delayed_active_state = ast_sip_session_media_state_alloc(); \ + delayed_active_state->topology = ast_stream_topology_alloc(); \ + current_active_state = ast_sip_session_media_state_alloc(); \ + current_active_state->topology = ast_stream_topology_alloc(); \ + expected_pending_state = ast_sip_session_media_state_alloc(); \ + expected_pending_state->topology = ast_stream_topology_alloc(); \ +}) + +#define CHECKER() \ +({ \ + new_pending_state = resolve_refresh_media_states("unittest", delayed_pending_state, delayed_active_state, current_active_state, 1); \ + if (!test_is_media_state_equal(new_pending_state, expected_pending_state, 0)) { \ + res = AST_TEST_FAIL; \ + test_failed = 1; \ + ast_test_status_update(test, "da: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(delayed_active_state->topology, &STR_TMP))); \ + ast_test_status_update(test, "dp: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(delayed_pending_state->topology, &STR_TMP))); \ + ast_test_status_update(test, "ca: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(current_active_state->topology, &STR_TMP))); \ + ast_test_status_update(test, "ep: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(expected_pending_state->topology, &STR_TMP))); \ + ast_test_status_update(test, "np: %s\n", ast_str_tmp(256, ast_stream_topology_to_str(new_pending_state->topology, &STR_TMP))); \ + } \ + ast_test_status_update(test, "Test %d %s\n", testnum, test_failed ? "FAILED" : "passed"); \ + ast_trace(-1, "Test %d %s\n", testnum, test_failed ? "FAILED" : "passed"); \ + test_failed = 0; \ + FREE_STATE(); \ +}) + + + struct ast_sip_session_media_state * delayed_pending_state = NULL; + struct ast_sip_session_media_state * delayed_active_state = NULL; + struct ast_sip_session_media_state * current_active_state = NULL; + struct ast_sip_session_media_state * new_pending_state = NULL; + struct ast_sip_session_media_state * expected_pending_state = NULL; + enum ast_test_result_state res = AST_TEST_PASS; + int test_failed = 0; + int testnum = 0; + SCOPE_ENTER(1); + + switch (cmd) { + case TEST_INIT: + info->name = "merge_refresh_topologies"; + info->category = "/res/res_pjsip_session/"; + info->summary = "Test merging of delayed request topologies"; + info->description = "Test merging of delayed request topologies"; + SCOPE_EXIT_RTN_VALUE(AST_TEST_NOT_RUN); + case TEST_EXECUTE: + break; + } + + RESET_STATE(1); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(2); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(3); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(4); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(5); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + CHECKER(); + + RESET_STATE(6); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + test_media_add(current_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(7); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(8); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(9); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(10); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, -1); + test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(11); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "myvideo3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(12); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "297-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "294-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "290-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "297-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "292-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "296-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "290-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "297-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "294-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(13); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "298-7", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "290-6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "293-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "292-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "294-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "295-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "296-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "290-6", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "298-7", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(14); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + RESET_STATE(15); + test_media_add(delayed_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(delayed_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDONLY, -1); + test_media_add(delayed_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(delayed_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(current_active_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(current_active_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + + test_media_add(expected_pending_state, "audio", AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "297-2", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "291-3", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "294-4", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + test_media_add(expected_pending_state, "298-1", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDONLY, -1); + test_media_add(expected_pending_state, "295-5", AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, -1); + CHECKER(); + + SCOPE_EXIT_RTN_VALUE(res); +} +#endif /* TEST_FRAMEWORK */ + static int load_module(void) { pjsip_endpoint *endpt; @@ -4737,12 +5863,17 @@ static int load_module(void) ast_sip_register_service(&outbound_invite_auth_module); ast_module_shutdown_ref(ast_module_info->self); - +#ifdef TEST_FRAMEWORK + AST_TEST_REGISTER(test_resolve_refresh_media_states); +#endif return AST_MODULE_LOAD_SUCCESS; } static int unload_module(void) { +#ifdef TEST_FRAMEWORK + AST_TEST_UNREGISTER(test_resolve_refresh_media_states); +#endif ast_sip_unregister_service(&outbound_invite_auth_module); ast_sip_unregister_service(&session_reinvite_module); ast_sip_unregister_service(&session_module); -- GitLab