diff --git a/bridges/bridge_softmix.c b/bridges/bridge_softmix.c index 817f8b2a91dae1f5b15d34df548ad0670bad537f..931cf62dfc41104a2498b45f753243fbb220da57 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 205feeb9c4dc1ba0a56db1e98fee27bc0e3a2c3d..387c5b378e24d1734c5dc828a9302ecf844923f5 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 859018d5ffaff1533b4a2ca5e9f6782ba4d5547e..e464c5e8d85479a0da236c72ecf9c0d12221ddd6 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 f07ee38adbe8bec598689e4de6091dcbc0210164..0385a96a8d32a0b3bcbfc149c20e22550e13d8c5 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);