diff options
author | Joshua Colp <jcolp@digium.com> | 2017-07-26 08:20:35 -0500 |
---|---|---|
committer | Gerrit Code Review <gerrit2@gerrit.digium.api> | 2017-07-26 08:20:35 -0500 |
commit | 8412cc1e072a6f608473e98ef4b1366aea6e8549 (patch) | |
tree | ba1adcfdab54af95288293bb6c2152412dce99c8 /main | |
parent | 8c6dcbcc6e99078f55e077cc3493c44416e31552 (diff) | |
parent | 3a18a090309271420516ea345ec32c7afa9b332b (diff) |
Merge "SDP: Rework SDP offer/answer model and update capabilities merges."
Diffstat (limited to 'main')
-rw-r--r-- | main/sdp.c | 99 | ||||
-rw-r--r-- | main/sdp_options.c | 92 | ||||
-rw-r--r-- | main/sdp_private.h | 19 | ||||
-rw-r--r-- | main/sdp_state.c | 2151 | ||||
-rw-r--r-- | main/stream.c | 17 |
5 files changed, 2014 insertions, 364 deletions
diff --git a/main/sdp.c b/main/sdp.c index bfb83e82f..fd10ba8c3 100644 --- a/main/sdp.c +++ b/main/sdp.c @@ -109,11 +109,9 @@ void ast_sdp_t_free(struct ast_sdp_t_line *t_line) ast_free(t_line); } -void ast_sdp_free(struct ast_sdp *sdp) +static void ast_sdp_dtor(void *vdoomed) { - if (!sdp) { - return; - } + struct ast_sdp *sdp = vdoomed; ast_sdp_o_free(sdp->o_line); ast_sdp_s_free(sdp->s_line); @@ -121,7 +119,6 @@ void ast_sdp_free(struct ast_sdp *sdp) ast_sdp_t_free(sdp->t_line); ast_sdp_a_lines_free(sdp->a_lines); ast_sdp_m_lines_free(sdp->m_lines); - ast_free(sdp); } #define COPY_STR_AND_ADVANCE(p, dest, source) \ @@ -314,28 +311,28 @@ struct ast_sdp *ast_sdp_alloc(struct ast_sdp_o_line *o_line, { struct ast_sdp *new_sdp; - new_sdp = ast_calloc(1, sizeof *new_sdp); + new_sdp = ao2_alloc_options(sizeof(*new_sdp), ast_sdp_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK); if (!new_sdp) { return NULL; } new_sdp->a_lines = ast_calloc(1, sizeof(*new_sdp->a_lines)); if (!new_sdp->a_lines) { - ast_sdp_free(new_sdp); + ao2_ref(new_sdp, -1); return NULL; } if (AST_VECTOR_INIT(new_sdp->a_lines, 20)) { - ast_sdp_free(new_sdp); + ao2_ref(new_sdp, -1); return NULL; } new_sdp->m_lines = ast_calloc(1, sizeof(*new_sdp->m_lines)); if (!new_sdp->m_lines) { - ast_sdp_free(new_sdp); + ao2_ref(new_sdp, -1); return NULL; } if (AST_VECTOR_INIT(new_sdp->m_lines, 20)) { - ast_sdp_free(new_sdp); + ao2_ref(new_sdp, -1); return NULL; } @@ -741,6 +738,62 @@ static void process_fmtp_lines(const struct ast_sdp_m_line *m_line, int payload, } } +/*! + * \internal + * \brief Determine the RTP stream direction in the given a lines. + * \since 15.0.0 + * + * \param a_lines Attribute lines to search. + * + * \retval Stream direction on success. + * \retval AST_STREAM_STATE_REMOVED on failure. + * + * \return Nothing + */ +static enum ast_stream_state get_a_line_direction(const struct ast_sdp_a_lines *a_lines) +{ + if (a_lines) { + enum ast_stream_state direction; + int idx; + const struct ast_sdp_a_line *a_line; + + for (idx = 0; idx < AST_VECTOR_SIZE(a_lines); ++idx) { + a_line = AST_VECTOR_GET(a_lines, idx); + direction = ast_stream_str2state(a_line->name); + if (direction != AST_STREAM_STATE_REMOVED) { + return direction; + } + } + } + + return AST_STREAM_STATE_REMOVED; +} + +/*! + * \internal + * \brief Determine the RTP stream direction. + * \since 15.0.0 + * + * \param a_lines The SDP media global attributes + * \param m_line The SDP media section to convert + * + * \return Stream direction + */ +static enum ast_stream_state get_stream_direction(const struct ast_sdp_a_lines *a_lines, const struct ast_sdp_m_line *m_line) +{ + enum ast_stream_state direction; + + direction = get_a_line_direction(m_line->a_lines); + if (direction != AST_STREAM_STATE_REMOVED) { + return direction; + } + direction = get_a_line_direction(a_lines); + if (direction != AST_STREAM_STATE_REMOVED) { + return direction; + } + return AST_STREAM_STATE_SENDRECV; +} + /* * Needed so we don't have an external function referenced as data. * The dynamic linker doesn't handle that very well. @@ -758,13 +811,14 @@ static void rtp_codecs_free(struct ast_rtp_codecs *codecs) * Given an m-line from an SDP, convert it into an ast_stream structure. * This takes formats, as well as clock-rate and fmtp attributes into account. * + * \param a_lines The SDP media global attributes * \param m_line The SDP media section to convert * \param g726_non_standard Non-zero if G.726 is non-standard * * \retval NULL An error occurred * \retval non-NULL The converted stream */ -static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line, int g726_non_standard) +static struct ast_stream *get_stream_from_m(const struct ast_sdp_a_lines *a_lines, const struct ast_sdp_m_line *m_line, int g726_non_standard) { int i; int non_ast_fmts; @@ -793,6 +847,14 @@ static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line, ao2_ref(caps, -1); return NULL; } + ast_stream_set_data(stream, AST_STREAM_DATA_RTP_CODECS, codecs, + (ast_stream_data_free_fn) rtp_codecs_free); + + if (!m_line->port) { + /* Stream is declined. There may not be any attributes. */ + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); + break; + } options = g726_non_standard ? AST_RTP_OPT_G726_NONSTANDARD : 0; for (i = 0; i < ast_sdp_m_get_payload_count(m_line); ++i) { @@ -819,10 +881,16 @@ static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line, } ast_rtp_codecs_payload_formats(codecs, caps, &non_ast_fmts); - ast_stream_set_data(stream, AST_STREAM_DATA_RTP_CODECS, codecs, - (ast_stream_data_free_fn) rtp_codecs_free); + + ast_stream_set_state(stream, get_stream_direction(a_lines, m_line)); break; case AST_MEDIA_TYPE_IMAGE: + if (!m_line->port) { + /* Stream is declined. There may not be any attributes. */ + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); + break; + } + for (i = 0; i < ast_sdp_m_get_payload_count(m_line); ++i) { struct ast_sdp_payload *payload; @@ -830,12 +898,15 @@ static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line, payload = ast_sdp_m_get_payload(m_line, i); if (!strcasecmp(payload->fmt, "t38")) { ast_format_cap_append(caps, ast_format_t38, 0); + ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV); } } break; case AST_MEDIA_TYPE_UNKNOWN: case AST_MEDIA_TYPE_TEXT: case AST_MEDIA_TYPE_END: + /* Consider these unsupported streams as declined */ + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); break; } @@ -858,7 +929,7 @@ struct ast_stream_topology *ast_get_topology_from_sdp(const struct ast_sdp *sdp, for (i = 0; i < ast_sdp_get_m_count(sdp); ++i) { struct ast_stream *stream; - stream = get_stream_from_m(ast_sdp_get_m(sdp, i), g726_non_standard); + stream = get_stream_from_m(sdp->a_lines, ast_sdp_get_m(sdp, i), g726_non_standard); if (!stream) { /* * The topology cannot match the SDP because diff --git a/main/sdp_options.c b/main/sdp_options.c index a938583c6..79efbafa0 100644 --- a/main/sdp_options.c +++ b/main/sdp_options.c @@ -27,6 +27,7 @@ #define DEFAULT_ICE AST_SDP_ICE_DISABLED #define DEFAULT_IMPL AST_SDP_IMPL_STRING #define DEFAULT_ENCRYPTION AST_SDP_ENCRYPTION_DISABLED +#define DEFAULT_MAX_STREAMS 16 /* Set to match our PJPROJECT PJMEDIA_MAX_SDP_MEDIA. */ #define DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(field, assert_on_null) \ void ast_sdp_options_set_##field(struct ast_sdp_options *options, const char *value) \ @@ -60,6 +61,12 @@ DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(sdpowner, 0); DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(sdpsession, 0); DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(rtp_engine, 0); +DEFINE_GETTERS_SETTERS_FOR(void *, state_context); +DEFINE_GETTERS_SETTERS_FOR(ast_sdp_answerer_modify_cb, answerer_modify_cb); +DEFINE_GETTERS_SETTERS_FOR(ast_sdp_offerer_modify_cb, offerer_modify_cb); +DEFINE_GETTERS_SETTERS_FOR(ast_sdp_offerer_config_cb, offerer_config_cb); +DEFINE_GETTERS_SETTERS_FOR(ast_sdp_preapply_cb, preapply_cb); +DEFINE_GETTERS_SETTERS_FOR(ast_sdp_postapply_cb, postapply_cb); DEFINE_GETTERS_SETTERS_FOR(unsigned int, rtp_symmetric); DEFINE_GETTERS_SETTERS_FOR(unsigned int, udptl_symmetric); DEFINE_GETTERS_SETTERS_FOR(enum ast_t38_ec_modes, udptl_error_correction); @@ -71,6 +78,7 @@ DEFINE_GETTERS_SETTERS_FOR(unsigned int, tos_audio); DEFINE_GETTERS_SETTERS_FOR(unsigned int, cos_audio); DEFINE_GETTERS_SETTERS_FOR(unsigned int, tos_video); DEFINE_GETTERS_SETTERS_FOR(unsigned int, cos_video); +DEFINE_GETTERS_SETTERS_FOR(unsigned int, max_streams); DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_dtmf, dtmf); DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_ice, ice); DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_impl, impl); @@ -110,12 +118,87 @@ void ast_sdp_options_set_sched_type(struct ast_sdp_options *options, enum ast_me } } +struct ast_format_cap *ast_sdp_options_get_format_cap_type(const struct ast_sdp_options *options, + enum ast_media_type type) +{ + struct ast_format_cap *cap = NULL; + + switch (type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + case AST_MEDIA_TYPE_IMAGE: + case AST_MEDIA_TYPE_TEXT: + cap = options->caps[type]; + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_END: + break; + } + return cap; +} + +void ast_sdp_options_set_format_cap_type(struct ast_sdp_options *options, + enum ast_media_type type, struct ast_format_cap *cap) +{ + switch (type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + case AST_MEDIA_TYPE_IMAGE: + case AST_MEDIA_TYPE_TEXT: + ao2_cleanup(options->caps[type]); + options->caps[type] = NULL; + if (cap && !ast_format_cap_empty(cap)) { + ao2_ref(cap, +1); + options->caps[type] = cap; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_END: + break; + } +} + +void ast_sdp_options_set_format_caps(struct ast_sdp_options *options, + struct ast_format_cap *cap) +{ + enum ast_media_type type; + + for (type = AST_MEDIA_TYPE_UNKNOWN; type < AST_MEDIA_TYPE_END; ++type) { + ao2_cleanup(options->caps[type]); + options->caps[type] = NULL; + } + + if (!cap || ast_format_cap_empty(cap)) { + return; + } + + for (type = AST_MEDIA_TYPE_UNKNOWN + 1; type < AST_MEDIA_TYPE_END; ++type) { + struct ast_format_cap *type_cap; + + type_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!type_cap) { + continue; + } + + ast_format_cap_set_framing(type_cap, ast_format_cap_get_framing(cap)); + if (ast_format_cap_append_from_cap(type_cap, cap, type) + || ast_format_cap_empty(type_cap)) { + ao2_ref(type_cap, -1); + continue; + } + + /* This takes the allocation reference */ + options->caps[type] = type_cap; + } +} + static void set_defaults(struct ast_sdp_options *options) { options->dtmf = DEFAULT_DTMF; options->ice = DEFAULT_ICE; options->impl = DEFAULT_IMPL; options->encryption = DEFAULT_ENCRYPTION; + options->max_streams = DEFAULT_MAX_STREAMS; } struct ast_sdp_options *ast_sdp_options_alloc(void) @@ -138,6 +221,15 @@ struct ast_sdp_options *ast_sdp_options_alloc(void) void ast_sdp_options_free(struct ast_sdp_options *options) { + enum ast_media_type type; + + if (!options) { + return; + } + + for (type = AST_MEDIA_TYPE_UNKNOWN; type < AST_MEDIA_TYPE_END; ++type) { + ao2_cleanup(options->caps[type]); + } ast_string_field_free_memory(options); ast_free(options); } diff --git a/main/sdp_private.h b/main/sdp_private.h index 62228a5c8..48bedc802 100644 --- a/main/sdp_private.h +++ b/main/sdp_private.h @@ -24,7 +24,7 @@ struct ast_sdp_options { AST_DECLARE_STRING_FIELDS( - /*! Media address to use in SDP */ + /*! Media address to advertise in SDP session c= line */ AST_STRING_FIELD(media_address); /*! Optional address of the interface media should use. */ AST_STRING_FIELD(interface_address); @@ -37,12 +37,25 @@ struct ast_sdp_options { ); /*! Scheduler context for the media stream types (Mainly for RTP) */ struct ast_sched_context *sched[AST_MEDIA_TYPE_END]; + /*! Capabilities to create new streams of the indexed media type. */ + struct ast_format_cap *caps[AST_MEDIA_TYPE_END]; + /*! User supplied context data pointer for the SDP state. */ + void *state_context; + /*! Modify negotiated topology before create answer SDP callback. */ + ast_sdp_answerer_modify_cb answerer_modify_cb; + /*! Modify proposed topology before create offer SDP callback. */ + ast_sdp_offerer_modify_cb offerer_modify_cb; + /*! Configure proposed topology extra stream options before create offer SDP callback. */ + ast_sdp_offerer_config_cb offerer_config_cb; + /*! Negotiated topology is about to be applied callback. */ + ast_sdp_preapply_cb preapply_cb; + /*! Negotiated topology was just applied callback. */ + ast_sdp_postapply_cb postapply_cb; struct { unsigned int rtp_symmetric:1; unsigned int udptl_symmetric:1; unsigned int rtp_ipv6:1; unsigned int g726_non_standard:1; - unsigned int locally_held:1; unsigned int rtcp_mux:1; unsigned int ssrc:1; }; @@ -52,6 +65,8 @@ struct ast_sdp_options { unsigned int tos_video; unsigned int cos_video; unsigned int udptl_far_max_datagram; + /*! Maximum number of streams to allow. */ + unsigned int max_streams; }; enum ast_sdp_options_dtmf dtmf; enum ast_sdp_options_ice ice; diff --git a/main/sdp_state.c b/main/sdp_state.c index 99421ad4d..330140c69 100644 --- a/main/sdp_state.c +++ b/main/sdp_state.c @@ -85,12 +85,39 @@ struct sdp_state_stream { }; /*! An explicit connection address for this stream */ struct ast_sockaddr connection_address; - /*! Whether this stream is held or not */ - unsigned int locally_held; + /*! + * \brief Stream is on hold by remote side + * + * \note This flag is never set on the + * sdp_state->proposed_capabilities->streams states. This is useful + * when the remote sends us a reINVITE with a deferred SDP to place + * us on and off of hold. + */ + unsigned int remotely_held:1; + /*! Stream is on hold by local side */ + unsigned int locally_held:1; /*! UDPTL session parameters */ struct ast_control_t38_parameters t38_local_params; }; +static int sdp_is_stream_type_supported(enum ast_media_type type) +{ + int is_supported = 0; + + switch (type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + case AST_MEDIA_TYPE_IMAGE: + is_supported = 1; + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + return is_supported; +} + static void sdp_state_rtp_destroy(void *obj) { struct sdp_state_rtp *rtp = obj; @@ -135,8 +162,6 @@ struct sdp_state_capabilities { struct ast_stream_topology *topology; /*! Additional information about the streams */ struct sdp_state_streams streams; - /*! An explicit global connection address */ - struct ast_sockaddr connection_address; }; static void sdp_state_capabilities_free(struct sdp_state_capabilities *capabilities) @@ -269,30 +294,80 @@ static struct sdp_state_udptl *create_udptl(const struct ast_sdp_options *option return udptl; } +static struct ast_stream *merge_local_stream(const struct ast_sdp_options *options, + const struct ast_stream *update); + static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const struct ast_stream_topology *topology, const struct ast_sdp_options *options) { struct sdp_state_capabilities *capabilities; - int i; + struct ast_stream *stream; + unsigned int topology_count; + unsigned int max_streams; + unsigned int idx; capabilities = ast_calloc(1, sizeof(*capabilities)); if (!capabilities) { return NULL; } - capabilities->topology = ast_stream_topology_clone(topology); + capabilities->topology = ast_stream_topology_alloc(); if (!capabilities->topology) { sdp_state_capabilities_free(capabilities); return NULL; } - if (AST_VECTOR_INIT(&capabilities->streams, ast_stream_topology_get_count(topology))) { + max_streams = ast_sdp_options_get_max_streams(options); + if (topology) { + topology_count = ast_stream_topology_get_count(topology); + } else { + topology_count = 0; + } + + /* Gather acceptable streams from the initial topology */ + for (idx = 0; idx < topology_count; ++idx) { + stream = ast_stream_topology_get_stream(topology, idx); + if (!sdp_is_stream_type_supported(ast_stream_get_type(stream))) { + /* Delete the unsupported stream from the initial topology */ + continue; + } + if (max_streams <= ast_stream_topology_get_count(capabilities->topology)) { + /* Cannot support any more streams */ + break; + } + + stream = merge_local_stream(options, stream); + if (!stream) { + sdp_state_capabilities_free(capabilities); + return NULL; + } + + if (ast_stream_topology_append_stream(capabilities->topology, stream) < 0) { + ast_stream_free(stream); + sdp_state_capabilities_free(capabilities); + return NULL; + } + } + + /* + * Remove trailing declined streams from the initial built topology. + * No need to waste space in the SDP with these unused slots. + */ + for (idx = ast_stream_topology_get_count(capabilities->topology); idx--;) { + stream = ast_stream_topology_get_stream(capabilities->topology, idx); + if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) { + break; + } + ast_stream_topology_del_stream(capabilities->topology, idx); + } + + topology_count = ast_stream_topology_get_count(capabilities->topology); + if (AST_VECTOR_INIT(&capabilities->streams, topology_count)) { sdp_state_capabilities_free(capabilities); return NULL; } - ast_sockaddr_setnull(&capabilities->connection_address); - for (i = 0; i < ast_stream_topology_get_count(topology); ++i) { + for (idx = 0; idx < topology_count; ++idx) { struct sdp_state_stream *state_stream; state_stream = ast_calloc(1, sizeof(*state_stream)); @@ -301,32 +376,34 @@ static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const st return NULL; } - state_stream->type = ast_stream_get_type(ast_stream_topology_get_stream(topology, i)); - switch (state_stream->type) { - case AST_MEDIA_TYPE_AUDIO: - case AST_MEDIA_TYPE_VIDEO: - state_stream->rtp = create_rtp(options, state_stream->type); - if (!state_stream->rtp) { - sdp_state_stream_free(state_stream); - sdp_state_capabilities_free(capabilities); - return NULL; - } - break; - case AST_MEDIA_TYPE_IMAGE: - state_stream->udptl = create_udptl(options); - if (!state_stream->udptl) { - sdp_state_stream_free(state_stream); - sdp_state_capabilities_free(capabilities); - return NULL; + stream = ast_stream_topology_get_stream(capabilities->topology, idx); + state_stream->type = ast_stream_get_type(stream); + if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) { + switch (state_stream->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + state_stream->rtp = create_rtp(options, state_stream->type); + if (!state_stream->rtp) { + sdp_state_stream_free(state_stream); + sdp_state_capabilities_free(capabilities); + return NULL; + } + break; + case AST_MEDIA_TYPE_IMAGE: + state_stream->udptl = create_udptl(options); + if (!state_stream->udptl) { + sdp_state_stream_free(state_stream); + sdp_state_capabilities_free(capabilities); + return NULL; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + /* Unsupported stream type already handled earlier */ + ast_assert(0); + break; } - break; - case AST_MEDIA_TYPE_UNKNOWN: - case AST_MEDIA_TYPE_TEXT: - case AST_MEDIA_TYPE_END: - ast_assert(0); - sdp_state_stream_free(state_stream); - sdp_state_capabilities_free(capabilities); - return NULL; } if (AST_VECTOR_APPEND(&capabilities->streams, state_stream)) { @@ -350,12 +427,12 @@ static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const st * is allocated, this topology is used to create the proposed_capabilities. * * If we are the SDP offerer, then the proposed_capabilities are what are used - * to generate the SDP offer. When the SDP answer arrives, the proposed capabilities - * are merged with the SDP answer to create the negotiated capabilities. + * to generate the offer SDP. When the answer SDP arrives, the proposed capabilities + * are merged with the answer SDP to create the negotiated capabilities. * - * If we are the SDP answerer, then the incoming SDP offer is merged with our + * If we are the SDP answerer, then the incoming offer SDP is merged with our * proposed capabilities to to create the negotiated capabilities. These negotiated - * capabilities are what we send in our SDP answer. + * capabilities are what we send in our answer SDP. * * Any changes that a user of the API performs will occur on the proposed capabilities. * The negotiated capabilities are only altered based on actual SDP negotiation. This is @@ -367,17 +444,33 @@ struct ast_sdp_state { struct sdp_state_capabilities *negotiated_capabilities; /*! Proposed capabilities */ struct sdp_state_capabilities *proposed_capabilities; - /*! Local SDP. Generated via the options and currently negotiated/proposed capabilities. */ + /*! + * \brief New topology waiting to be merged. + * + * \details + * Repeated topology updates are merged into each other here until + * negotiations are restarted and we create an offer. + */ + struct ast_stream_topology *pending_topology_update; + /*! Local SDP. Generated via the options and negotiated/proposed capabilities. */ struct ast_sdp *local_sdp; + /*! Saved remote SDP */ + struct ast_sdp *remote_sdp; /*! SDP options. Configured options beyond media capabilities. */ struct ast_sdp_options *options; /*! Translator that puts SDPs into the expected representation */ struct ast_sdp_translator *translator; + /*! An explicit global connection address */ + struct ast_sockaddr connection_address; /*! The role that we occupy in SDP negotiation */ enum ast_sdp_role role; + /*! TRUE if all streams on hold by local side */ + unsigned int locally_held:1; + /*! TRUE if the remote offer resulted in all streams being declined. */ + unsigned int remote_offer_rejected:1; }; -struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams, +struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *topology, struct ast_sdp_options *options) { struct ast_sdp_state *sdp_state; @@ -395,7 +488,7 @@ struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams, return NULL; } - sdp_state->proposed_capabilities = sdp_initialize_state_capabilities(streams, options); + sdp_state->proposed_capabilities = sdp_initialize_state_capabilities(topology, options); if (!sdp_state->proposed_capabilities) { ast_sdp_state_free(sdp_state); return NULL; @@ -414,12 +507,217 @@ void ast_sdp_state_free(struct ast_sdp_state *sdp_state) sdp_state_capabilities_free(sdp_state->negotiated_capabilities); sdp_state_capabilities_free(sdp_state->proposed_capabilities); - ast_sdp_free(sdp_state->local_sdp); + ao2_cleanup(sdp_state->local_sdp); + ao2_cleanup(sdp_state->remote_sdp); ast_sdp_options_free(sdp_state->options); ast_sdp_translator_free(sdp_state->translator); ast_free(sdp_state); } +/*! + * \internal + * \brief Allow a configured callback to alter the new negotiated joint topology. + * \since 15.0.0 + * + * \details + * The callback can alter topology stream names, formats, or decline streams. + * + * \param sdp_state + * \param topology Joint topology that we intend to generate the answer SDP. + * + * \return Nothing + */ +static void sdp_state_cb_answerer_modify_topology(const struct ast_sdp_state *sdp_state, + struct ast_stream_topology *topology) +{ + ast_sdp_answerer_modify_cb cb; + + cb = ast_sdp_options_get_answerer_modify_cb(sdp_state->options); + if (cb) { + void *context; + const struct ast_stream_topology *neg_topology;/*!< Last negotiated topology */ +#ifdef AST_DEVMODE + struct ast_stream *stream; + int idx; + enum ast_media_type type[ast_stream_topology_get_count(topology)]; + enum ast_stream_state state[ast_stream_topology_get_count(topology)]; + + /* + * Save stream types and states to validate that they don't + * get changed unexpectedly. + */ + for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) { + stream = ast_stream_topology_get_stream(topology, idx); + type[idx] = ast_stream_get_type(stream); + state[idx] = ast_stream_get_state(stream); + } +#endif + + context = ast_sdp_options_get_state_context(sdp_state->options); + neg_topology = sdp_state->negotiated_capabilities + ? sdp_state->negotiated_capabilities->topology : NULL; + cb(context, neg_topology, topology); + +#ifdef AST_DEVMODE + for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) { + stream = ast_stream_topology_get_stream(topology, idx); + + /* Check that active streams have at least one format */ + ast_assert(ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED + || (ast_stream_get_formats(stream) + && ast_format_cap_count(ast_stream_get_formats(stream)))); + + /* Check that stream types didn't change. */ + ast_assert(type[idx] == ast_stream_get_type(stream)); + + /* Check that streams didn't get resurected. */ + ast_assert(state[idx] != AST_STREAM_STATE_REMOVED + || ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED); + } +#endif + } +} + +/*! + * \internal + * \brief Allow a configured callback to alter the merged local topology. + * \since 15.0.0 + * + * \details + * The callback can modify streams in the merged topology. The + * callback can decline, add/remove/update formats, or rename + * streams. Changing anything else on the streams is likely to not + * end well. + * + * \param sdp_state + * \param topology Merged topology that we intend to generate the offer SDP. + * + * \return Nothing + */ +static void sdp_state_cb_offerer_modify_topology(const struct ast_sdp_state *sdp_state, + struct ast_stream_topology *topology) +{ + ast_sdp_offerer_modify_cb cb; + + cb = ast_sdp_options_get_offerer_modify_cb(sdp_state->options); + if (cb) { + void *context; + const struct ast_stream_topology *neg_topology;/*!< Last negotiated topology */ + + context = ast_sdp_options_get_state_context(sdp_state->options); + neg_topology = sdp_state->negotiated_capabilities + ? sdp_state->negotiated_capabilities->topology : NULL; + cb(context, neg_topology, topology); + +#ifdef AST_DEVMODE + { + struct ast_stream *stream; + int idx; + + /* Check that active streams have at least one format */ + for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) { + stream = ast_stream_topology_get_stream(topology, idx); + ast_assert(ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED + || (ast_stream_get_formats(stream) + && ast_format_cap_count(ast_stream_get_formats(stream)))); + } + } +#endif + } +} + +/*! + * \internal + * \brief Allow a configured callback to configure the merged local topology. + * \since 15.0.0 + * + * \details + * The callback can configure other parameters associated with each + * active stream on the topology. The callback can call several SDP + * API calls to configure the proposed capabilities of the streams + * before we create the offer SDP. For example, the callback could + * configure a stream specific connection address, T.38 parameters, + * RTP instance, or UDPTL instance parameters. + * + * \param sdp_state + * \param topology Merged topology that we intend to generate the offer SDP. + * + * \return Nothing + */ +static void sdp_state_cb_offerer_config_topology(const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *topology) +{ + ast_sdp_offerer_config_cb cb; + + cb = ast_sdp_options_get_offerer_config_cb(sdp_state->options); + if (cb) { + void *context; + + context = ast_sdp_options_get_state_context(sdp_state->options); + cb(context, topology); + } +} + +/*! + * \internal + * \brief Call any registered pre-apply topology callback. + * \since 15.0.0 + * + * \param sdp_state + * \param topology + * + * \return Nothing + */ +static void sdp_state_cb_preapply_topology(const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *topology) +{ + ast_sdp_preapply_cb cb; + + cb = ast_sdp_options_get_preapply_cb(sdp_state->options); + if (cb) { + void *context; + + context = ast_sdp_options_get_state_context(sdp_state->options); + cb(context, topology); + } +} + +/*! + * \internal + * \brief Call any registered post-apply topology callback. + * \since 15.0.0 + * + * \param sdp_state + * \param topology + * + * \return Nothing + */ +static void sdp_state_cb_postapply_topology(const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *topology) +{ + ast_sdp_postapply_cb cb; + + cb = ast_sdp_options_get_postapply_cb(sdp_state->options); + if (cb) { + void *context; + + context = ast_sdp_options_get_state_context(sdp_state->options); + cb(context, topology); + } +} + +static const struct sdp_state_capabilities *sdp_state_get_joint_capabilities( + const struct ast_sdp_state *sdp_state) +{ + ast_assert(sdp_state != NULL); + + if (sdp_state->negotiated_capabilities) { + return sdp_state->negotiated_capabilities; + } + + return sdp_state->proposed_capabilities; +} + static struct sdp_state_stream *sdp_state_get_stream(const struct ast_sdp_state *sdp_state, int stream_index) { if (stream_index >= AST_VECTOR_SIZE(&sdp_state->proposed_capabilities->streams)) { @@ -429,6 +727,18 @@ static struct sdp_state_stream *sdp_state_get_stream(const struct ast_sdp_state return AST_VECTOR_GET(&sdp_state->proposed_capabilities->streams, stream_index); } +static struct sdp_state_stream *sdp_state_get_joint_stream(const struct ast_sdp_state *sdp_state, int stream_index) +{ + const struct sdp_state_capabilities *capabilities; + + capabilities = sdp_state_get_joint_capabilities(sdp_state); + if (AST_VECTOR_SIZE(&capabilities->streams) <= stream_index) { + return NULL; + } + + return AST_VECTOR_GET(&capabilities->streams, stream_index); +} + struct ast_rtp_instance *ast_sdp_state_get_rtp_instance( const struct ast_sdp_state *sdp_state, int stream_index) { @@ -468,35 +778,34 @@ const struct ast_sockaddr *ast_sdp_state_get_connection_address(const struct ast { ast_assert(sdp_state != NULL); - return &sdp_state->proposed_capabilities->connection_address; + return &sdp_state->connection_address; } -int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_state, - int stream_index, struct ast_sockaddr *address) +static int sdp_state_stream_get_connection_address(const struct ast_sdp_state *sdp_state, + struct sdp_state_stream *stream_state, struct ast_sockaddr *address) { - struct sdp_state_stream *stream_state; - ast_assert(sdp_state != NULL); + ast_assert(stream_state != NULL); ast_assert(address != NULL); - stream_state = sdp_state_get_stream(sdp_state, stream_index); - if (!stream_state) { - return -1; - } - /* If an explicit connection address has been provided for the stream return it */ if (!ast_sockaddr_isnull(&stream_state->connection_address)) { ast_sockaddr_copy(address, &stream_state->connection_address); return 0; } - switch (ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->proposed_capabilities->topology, - stream_index))) { + switch (stream_state->type) { case AST_MEDIA_TYPE_AUDIO: case AST_MEDIA_TYPE_VIDEO: + if (!stream_state->rtp->instance) { + return -1; + } ast_rtp_instance_get_local_address(stream_state->rtp->instance, address); break; case AST_MEDIA_TYPE_IMAGE: + if (!stream_state->udptl->instance) { + return -1; + } ast_udptl_get_us(stream_state->udptl->instance, address); break; case AST_MEDIA_TYPE_UNKNOWN: @@ -505,27 +814,45 @@ int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_ return -1; } + if (ast_sockaddr_isnull(address)) { + /* No address is set on the stream state. */ + return -1; + } + /* If an explicit global connection address is set use it here for the IP part */ - if (!ast_sockaddr_isnull(&sdp_state->proposed_capabilities->connection_address)) { + if (!ast_sockaddr_isnull(&sdp_state->connection_address)) { int port = ast_sockaddr_port(address); - ast_sockaddr_copy(address, &sdp_state->proposed_capabilities->connection_address); + ast_sockaddr_copy(address, &sdp_state->connection_address); ast_sockaddr_set_port(address, port); } return 0; } -const struct ast_stream_topology *ast_sdp_state_get_joint_topology( - const struct ast_sdp_state *sdp_state) +int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_state, + int stream_index, struct ast_sockaddr *address) { + struct sdp_state_stream *stream_state; + ast_assert(sdp_state != NULL); + ast_assert(address != NULL); - if (sdp_state->negotiated_capabilities) { - return sdp_state->negotiated_capabilities->topology; + stream_state = sdp_state_get_stream(sdp_state, stream_index); + if (!stream_state) { + return -1; } - return sdp_state->proposed_capabilities->topology; + return sdp_state_stream_get_connection_address(sdp_state, stream_state, address); +} + +const struct ast_stream_topology *ast_sdp_state_get_joint_topology( + const struct ast_sdp_state *sdp_state) +{ + const struct sdp_state_capabilities *capabilities; + + capabilities = sdp_state_get_joint_capabilities(sdp_state); + return capabilities->topology; } const struct ast_stream_topology *ast_sdp_state_get_local_topology( @@ -544,50 +871,185 @@ const struct ast_sdp_options *ast_sdp_state_get_options( return sdp_state->options; } +static struct ast_stream *decline_stream(enum ast_media_type type, const char *name) +{ + struct ast_stream *stream; + + if (!name) { + name = ast_codec_media_type2str(type); + } + stream = ast_stream_alloc(name, type); + if (!stream) { + return NULL; + } + ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED); + return stream; +} + /*! - * \brief Merge two streams into a joint stream. + * \brief Merge an update stream into a local stream. + * + * \param options SDP Options + * \param update An updated stream * - * \param local Our local stream + * \retval NULL An error occurred + * \retval non-NULL The joint stream created + */ +static struct ast_stream *merge_local_stream(const struct ast_sdp_options *options, + const struct ast_stream *update) +{ + struct ast_stream *joint_stream; + struct ast_format_cap *joint_cap; + struct ast_format_cap *allowed_cap; + struct ast_format_cap *update_cap; + enum ast_stream_state joint_state; + + joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!joint_cap) { + return NULL; + } + + update_cap = ast_stream_get_formats(update); + allowed_cap = ast_sdp_options_get_format_cap_type(options, + ast_stream_get_type(update)); + if (allowed_cap && update_cap) { + struct ast_str *allowed_buf = ast_str_alloca(128); + struct ast_str *update_buf = ast_str_alloca(128); + struct ast_str *joint_buf = ast_str_alloca(128); + + ast_format_cap_get_compatible(allowed_cap, update_cap, joint_cap); + ast_debug(3, + "Filtered update '%s' with allowed '%s' to get joint '%s'. Joint has %zu formats\n", + ast_format_cap_get_names(update_cap, &update_buf), + ast_format_cap_get_names(allowed_cap, &allowed_buf), + ast_format_cap_get_names(joint_cap, &joint_buf), + ast_format_cap_count(joint_cap)); + } + + /* Determine the joint stream state */ + joint_state = AST_STREAM_STATE_REMOVED; + if (ast_stream_get_state(update) != AST_STREAM_STATE_REMOVED + && ast_format_cap_count(joint_cap)) { + joint_state = AST_STREAM_STATE_SENDRECV; + } + + joint_stream = ast_stream_alloc(ast_stream_get_name(update), + ast_stream_get_type(update)); + if (joint_stream) { + ast_stream_set_state(joint_stream, joint_state); + if (joint_state != AST_STREAM_STATE_REMOVED) { + ast_stream_set_formats(joint_stream, joint_cap); + } + } + + ao2_ref(joint_cap, -1); + + return joint_stream; +} + +/*! + * \brief Merge a remote stream into a local stream. + * + * \param sdp_state + * \param local Our local stream (NULL if creating new stream) + * \param locally_held Nonzero if the local stream is held * \param remote A remote stream * * \retval NULL An error occurred * \retval non-NULL The joint stream created */ -static struct ast_stream *merge_streams(const struct ast_stream *local, +static struct ast_stream *merge_remote_stream(const struct ast_sdp_state *sdp_state, + const struct ast_stream *local, unsigned int locally_held, const struct ast_stream *remote) { struct ast_stream *joint_stream; struct ast_format_cap *joint_cap; struct ast_format_cap *local_cap; struct ast_format_cap *remote_cap; - struct ast_str *local_buf = ast_str_alloca(128); - struct ast_str *remote_buf = ast_str_alloca(128); - struct ast_str *joint_buf = ast_str_alloca(128); - - joint_stream = ast_stream_alloc(ast_codec_media_type2str(ast_stream_get_type(remote)), - ast_stream_get_type(remote)); - if (!joint_stream) { - return NULL; - } + const char *joint_name; + enum ast_stream_state joint_state; + enum ast_stream_state remote_state; joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); if (!joint_cap) { - ast_stream_free(joint_stream); return NULL; } - local_cap = ast_stream_get_formats(local); remote_cap = ast_stream_get_formats(remote); + if (local) { + local_cap = ast_stream_get_formats(local); + } else { + local_cap = ast_sdp_options_get_format_cap_type(sdp_state->options, + ast_stream_get_type(remote)); + } + if (local_cap && remote_cap) { + struct ast_str *local_buf = ast_str_alloca(128); + struct ast_str *remote_buf = ast_str_alloca(128); + struct ast_str *joint_buf = ast_str_alloca(128); + + ast_format_cap_get_compatible(local_cap, remote_cap, joint_cap); + ast_debug(3, + "Combined local '%s' with remote '%s' to get joint '%s'. Joint has %zu formats\n", + ast_format_cap_get_names(local_cap, &local_buf), + ast_format_cap_get_names(remote_cap, &remote_buf), + ast_format_cap_get_names(joint_cap, &joint_buf), + ast_format_cap_count(joint_cap)); + } + + /* Determine the joint stream state */ + remote_state = ast_stream_get_state(remote); + joint_state = AST_STREAM_STATE_REMOVED; + if ((!local || ast_stream_get_state(local) != AST_STREAM_STATE_REMOVED) + && ast_format_cap_count(joint_cap)) { + if (sdp_state->locally_held || locally_held) { + switch (remote_state) { + case AST_STREAM_STATE_REMOVED: + break; + case AST_STREAM_STATE_INACTIVE: + joint_state = AST_STREAM_STATE_INACTIVE; + break; + case AST_STREAM_STATE_SENDRECV: + joint_state = AST_STREAM_STATE_SENDONLY; + break; + case AST_STREAM_STATE_SENDONLY: + joint_state = AST_STREAM_STATE_INACTIVE; + break; + case AST_STREAM_STATE_RECVONLY: + joint_state = AST_STREAM_STATE_SENDONLY; + break; + } + } else { + switch (remote_state) { + case AST_STREAM_STATE_REMOVED: + break; + case AST_STREAM_STATE_INACTIVE: + joint_state = AST_STREAM_STATE_RECVONLY; + break; + case AST_STREAM_STATE_SENDRECV: + joint_state = AST_STREAM_STATE_SENDRECV; + break; + case AST_STREAM_STATE_SENDONLY: + joint_state = AST_STREAM_STATE_RECVONLY; + break; + case AST_STREAM_STATE_RECVONLY: + joint_state = AST_STREAM_STATE_SENDRECV; + break; + } + } + } - ast_format_cap_get_compatible(local_cap, remote_cap, joint_cap); - - ast_debug(3, "Combined local '%s' with remote '%s' to get joint '%s'. Joint has %zu formats\n", - ast_format_cap_get_names(local_cap, &local_buf), - ast_format_cap_get_names(remote_cap, &remote_buf), - ast_format_cap_get_names(joint_cap, &joint_buf), - ast_format_cap_count(joint_cap)); - - ast_stream_set_formats(joint_stream, joint_cap); + if (local) { + joint_name = ast_stream_get_name(local); + } else { + joint_name = ast_codec_media_type2str(ast_stream_get_type(remote)); + } + joint_stream = ast_stream_alloc(joint_name, ast_stream_get_type(remote)); + if (joint_stream) { + ast_stream_set_state(joint_stream, joint_state); + if (joint_state != AST_STREAM_STATE_REMOVED) { + ast_stream_set_formats(joint_stream, joint_cap); + } + } ao2_ref(joint_cap, -1); @@ -595,103 +1057,916 @@ static struct ast_stream *merge_streams(const struct ast_stream *local, } /*! - * \brief Get a local stream that corresponds with a remote stream. + * \internal + * \brief Determine if a merged topology should be rejected. + * \since 15.0.0 * - * \param local The local topology - * \param media_type The type of stream we are looking for - * \param[in,out] media_indices Keeps track of where to start searching in the topology + * \param topology What topology to determine if we reject * - * \retval -1 No corresponding stream found - * \retval index The corresponding stream index + * \retval 0 if not rejected. + * \retval non-zero if rejected. */ -static int get_corresponding_index(const struct ast_stream_topology *local, - enum ast_media_type media_type, int *media_indices) +static int sdp_topology_is_rejected(struct ast_stream_topology *topology) { - int i; + int idx; + struct ast_stream *stream; - for (i = media_indices[media_type]; i < ast_stream_topology_get_count(local); ++i) { - struct ast_stream *candidate; + for (idx = ast_stream_topology_get_count(topology); idx--;) { + stream = ast_stream_topology_get_stream(topology, idx); + if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) { + /* At least one stream is not declined */ + return 0; + } + } + + /* All streams are declined */ + return 1; +} + +static void sdp_state_stream_copy_common(struct sdp_state_stream *dst, const struct sdp_state_stream *src) +{ + ast_sockaddr_copy(&dst->connection_address, + &src->connection_address); + /* Explicitly does not copy the local or remote hold states. */ + dst->t38_local_params = src->t38_local_params; +} - candidate = ast_stream_topology_get_stream(local, i); - if (ast_stream_get_type(candidate) == media_type) { - media_indices[media_type] = i + 1; - return i; +static void sdp_state_stream_copy(struct sdp_state_stream *dst, const struct sdp_state_stream *src) +{ + *dst = *src; + + switch (dst->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + ao2_bump(dst->rtp); + break; + case AST_MEDIA_TYPE_IMAGE: + ao2_bump(dst->udptl); + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } +} + +/*! + * \internal + * \brief Initialize an int vector and default the contents to the member index. + * \since 15.0.0 + * + * \param vect Vetctor to initialize and set to default values. + * \param size Size of the vector to setup. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int sdp_vect_idx_init(struct ast_vector_int *vect, size_t size) +{ + int idx; + + if (AST_VECTOR_INIT(vect, size)) { + return -1; + } + for (idx = 0; idx < size; ++idx) { + AST_VECTOR_APPEND(vect, idx); + } + return 0; +} + +/*! + * \internal + * \brief Compare stream types for sort order. + * \since 15.0.0 + * + * \param left Stream parameter on left + * \param right Stream parameter on right + * + * \retval <0 left stream sorts first. + * \retval =0 streams match. + * \retval >0 right stream sorts first. + */ +static int sdp_stream_cmp_by_type(const struct ast_stream *left, const struct ast_stream *right) +{ + enum ast_media_type left_type = ast_stream_get_type(left); + enum ast_media_type right_type = ast_stream_get_type(right); + + /* Treat audio and image as the same for T.38 support */ + if (left_type == AST_MEDIA_TYPE_IMAGE) { + left_type = AST_MEDIA_TYPE_AUDIO; + } + if (right_type == AST_MEDIA_TYPE_IMAGE) { + right_type = AST_MEDIA_TYPE_AUDIO; + } + + return left_type - right_type; +} + +/*! + * \internal + * \brief Compare stream names and types for sort order. + * \since 15.0.0 + * + * \param left Stream parameter on left + * \param right Stream parameter on right + * + * \retval <0 left stream sorts first. + * \retval =0 streams match. + * \retval >0 right stream sorts first. + */ +static int sdp_stream_cmp_by_name(const struct ast_stream *left, const struct ast_stream *right) +{ + int cmp; + const char *left_name; + + left_name = ast_stream_get_name(left); + cmp = strcmp(left_name, ast_stream_get_name(right)); + if (!cmp) { + cmp = sdp_stream_cmp_by_type(left, right); + if (!cmp) { + /* Are the stream names real or type names which aren't matchable? */ + if (ast_strlen_zero(left_name) + || !strcmp(left_name, ast_codec_media_type2str(ast_stream_get_type(left))) + || !strcmp(left_name, ast_codec_media_type2str(ast_stream_get_type(right)))) { + /* The streams don't actually have real names */ + cmp = -1; + } } } + return cmp; +} - /* No stream of the type left in the topology */ - media_indices[media_type] = i; - return -1; +/*! + * \internal + * \brief Merge topology streams by the match function. + * \since 15.0.0 + * + * \param sdp_state + * \param current_topology Topology to update with state. + * \param update_topology Topology to merge into the current topology. + * \param current_vect Stream index vector of remaining current_topology streams. + * \param update_vect Stream index vector of remaining update_topology streams. + * \param backfill_candidate Array of flags marking current_topology streams + * that can be reused for a different stream. + * \param match Stream comparison function to identify corresponding streams + * between the current_topology and update_topology. + * \param merged_topology Output topology of merged streams. + * \param compact_streams TRUE if backfill and limit number of streams. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int sdp_merge_streams_match( + const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *current_topology, + const struct ast_stream_topology *update_topology, + struct ast_vector_int *current_vect, + struct ast_vector_int *update_vect, + char backfill_candidate[], + int (*match)(const struct ast_stream *left, const struct ast_stream *right), + struct ast_stream_topology *merged_topology, + int compact_streams) +{ + struct ast_stream *current_stream; + struct ast_stream *update_stream; + int current_idx; + int update_idx; + int idx; + + for (current_idx = 0; current_idx < AST_VECTOR_SIZE(current_vect);) { + idx = AST_VECTOR_GET(current_vect, current_idx); + current_stream = ast_stream_topology_get_stream(current_topology, idx); + + for (update_idx = 0; update_idx < AST_VECTOR_SIZE(update_vect); ++update_idx) { + idx = AST_VECTOR_GET(update_vect, update_idx); + update_stream = ast_stream_topology_get_stream(update_topology, idx); + + if (match(current_stream, update_stream)) { + continue; + } + + if (!compact_streams + || ast_stream_get_state(current_stream) != AST_STREAM_STATE_REMOVED + || ast_stream_get_state(update_stream) != AST_STREAM_STATE_REMOVED) { + struct ast_stream *merged_stream; + + merged_stream = merge_local_stream(sdp_state->options, update_stream); + if (!merged_stream) { + return -1; + } + idx = AST_VECTOR_GET(current_vect, current_idx); + ast_stream_topology_set_stream(merged_topology, idx, merged_stream); + + /* + * The current_stream cannot be considered a backfill_candidate + * anymore since it got updated. + * + * XXX It could be argued that if the declined status didn't + * change because the merged_stream became declined then we + * shouldn't remove the stream slot as a backfill_candidate + * and we shouldn't update the merged_topology stream. If we + * then backfilled the stream we would likely mess up the core + * if it is matching streams by type since the core attempted + * to update the stream with an incompatible stream. Any + * backfilled streams could cause a stream type ordering + * problem. However, we do need to reclaim declined stream + * slots sometime. + */ + backfill_candidate[idx] = 0; + } + + AST_VECTOR_REMOVE_ORDERED(current_vect, current_idx); + AST_VECTOR_REMOVE_ORDERED(update_vect, update_idx); + goto matched_next; + } + + ++current_idx; +matched_next:; + } + return 0; +} + +/*! + * \internal + * \brief Merge the current local topology with an updated topology. + * \since 15.0.0 + * + * \param sdp_state + * \param current_topology Topology to update with state. + * \param update_topology Topology to merge into the current topology. + * \param compact_streams TRUE if backfill and limit number of streams. + * + * \retval merged topology on success. + * \retval NULL on failure. + */ +static struct ast_stream_topology *merge_local_topologies( + const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *current_topology, + const struct ast_stream_topology *update_topology, + int compact_streams) +{ + struct ast_stream_topology *merged_topology; + struct ast_stream *current_stream; + struct ast_stream *update_stream; + struct ast_stream *merged_stream; + struct ast_vector_int current_vect; + struct ast_vector_int update_vect; + int current_idx = ast_stream_topology_get_count(current_topology); + int update_idx; + int idx; + char backfill_candidate[current_idx]; + + memset(backfill_candidate, 0, current_idx); + + if (compact_streams) { + /* Limit matching consideration to the maximum allowed live streams. */ + idx = ast_sdp_options_get_max_streams(sdp_state->options); + if (idx < current_idx) { + current_idx = idx; + } + } + if (sdp_vect_idx_init(¤t_vect, current_idx)) { + return NULL; + } + + if (sdp_vect_idx_init(&update_vect, ast_stream_topology_get_count(update_topology))) { + AST_VECTOR_FREE(¤t_vect); + return NULL; + } + + merged_topology = ast_stream_topology_clone(current_topology); + if (!merged_topology) { + goto fail; + } + + /* + * Remove any unsupported current streams from match consideration + * and mark potential backfill candidates. + */ + for (current_idx = AST_VECTOR_SIZE(¤t_vect); current_idx--;) { + idx = AST_VECTOR_GET(¤t_vect, current_idx); + current_stream = ast_stream_topology_get_stream(current_topology, idx); + if (ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED + && compact_streams) { + /* The declined stream is a potential backfill candidate */ + backfill_candidate[idx] = 1; + } + if (sdp_is_stream_type_supported(ast_stream_get_type(current_stream))) { + continue; + } + /* Unsupported current streams should always be declined */ + ast_assert(ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED); + + AST_VECTOR_REMOVE_ORDERED(¤t_vect, current_idx); + } + + /* Remove any unsupported update streams from match consideration. */ + for (update_idx = AST_VECTOR_SIZE(&update_vect); update_idx--;) { + idx = AST_VECTOR_GET(&update_vect, update_idx); + update_stream = ast_stream_topology_get_stream(update_topology, idx); + if (sdp_is_stream_type_supported(ast_stream_get_type(update_stream))) { + continue; + } + + AST_VECTOR_REMOVE_ORDERED(&update_vect, update_idx); + } + + /* Match by stream name and type */ + if (sdp_merge_streams_match(sdp_state, current_topology, update_topology, + ¤t_vect, &update_vect, backfill_candidate, sdp_stream_cmp_by_name, + merged_topology, compact_streams)) { + goto fail; + } + + /* Match by stream type */ + if (sdp_merge_streams_match(sdp_state, current_topology, update_topology, + ¤t_vect, &update_vect, backfill_candidate, sdp_stream_cmp_by_type, + merged_topology, compact_streams)) { + goto fail; + } + + /* Decline unmatched current stream slots */ + for (current_idx = AST_VECTOR_SIZE(¤t_vect); current_idx--;) { + idx = AST_VECTOR_GET(¤t_vect, current_idx); + current_stream = ast_stream_topology_get_stream(current_topology, idx); + + if (ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED) { + /* Stream is already declined. */ + continue; + } + + merged_stream = decline_stream(ast_stream_get_type(current_stream), + ast_stream_get_name(current_stream)); + if (!merged_stream) { + goto fail; + } + ast_stream_topology_set_stream(merged_topology, idx, merged_stream); + } + + /* Backfill new update stream slots into pre-existing declined current stream slots */ + while (AST_VECTOR_SIZE(&update_vect)) { + idx = ast_stream_topology_get_count(current_topology); + for (current_idx = 0; current_idx < idx; ++current_idx) { + if (backfill_candidate[current_idx]) { + break; + } + } + if (idx <= current_idx) { + /* No more backfill candidates remain. */ + break; + } + /* There should only be backfill stream slots when we are compact_streams */ + ast_assert(compact_streams); + + idx = AST_VECTOR_GET(&update_vect, 0); + update_stream = ast_stream_topology_get_stream(update_topology, idx); + AST_VECTOR_REMOVE_ORDERED(&update_vect, 0); + + if (ast_stream_get_state(update_stream) == AST_STREAM_STATE_REMOVED) { + /* New stream is already declined so don't bother adding it. */ + continue; + } + + merged_stream = merge_local_stream(sdp_state->options, update_stream); + if (!merged_stream) { + goto fail; + } + if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) { + /* New stream not compatible so don't bother adding it. */ + ast_stream_free(merged_stream); + continue; + } + + /* Add the new stream into the backfill stream slot. */ + ast_stream_topology_set_stream(merged_topology, current_idx, merged_stream); + backfill_candidate[current_idx] = 0; + } + + /* Append any remaining new update stream slots that can fit. */ + while (AST_VECTOR_SIZE(&update_vect) + && (!compact_streams + || ast_stream_topology_get_count(merged_topology) + < ast_sdp_options_get_max_streams(sdp_state->options))) { + idx = AST_VECTOR_GET(&update_vect, 0); + update_stream = ast_stream_topology_get_stream(update_topology, idx); + AST_VECTOR_REMOVE_ORDERED(&update_vect, 0); + + if (ast_stream_get_state(update_stream) == AST_STREAM_STATE_REMOVED) { + /* New stream is already declined so don't bother adding it. */ + continue; + } + + merged_stream = merge_local_stream(sdp_state->options, update_stream); + if (!merged_stream) { + goto fail; + } + if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) { + /* New stream not compatible so don't bother adding it. */ + ast_stream_free(merged_stream); + continue; + } + + /* Append the new update stream. */ + if (ast_stream_topology_append_stream(merged_topology, merged_stream) < 0) { + ast_stream_free(merged_stream); + goto fail; + } + } + + AST_VECTOR_FREE(¤t_vect); + AST_VECTOR_FREE(&update_vect); + return merged_topology; + +fail: + ast_stream_topology_free(merged_topology); + AST_VECTOR_FREE(¤t_vect); + AST_VECTOR_FREE(&update_vect); + return NULL; } /*! - * XXX TODO The merge_capabilities() function needs to be split into - * merging for new local topologies and new remote topologies. Also - * the possibility of changing the stream types needs consideration. - * Audio to video may or may not need us to keep the same RTP instance - * because the stream position is still RTP. A new RTP instance would - * cause us to change ports. Audio to image is definitely going to - * happen for T.38. - * - * A new remote topology as an initial offer needs to dictate the - * number of streams and the order. As a sdp_state option we may - * allow creation of new active streams not defined by the current - * local topology. A subsequent remote offer can change the stream - * types and add streams. The sdp_state option could regulate - * creation of new active streams here as well. An answer cannot - * change stream types or the number of streams but can decline - * streams. Any attempt to do so should report an error and possibly - * disconnect the call. - * - * A local topology update needs to be merged differently. It cannot - * reduce the number of streams already defined without violating the - * SDP RFC. The local merge could take the new topology stream - * verbatim and add declined streams to fill out any shortfall with - * the exiting topology. This strategy is needed if we want to change - * an audio stream to an image stream for T.38 fax and vice versa. - * The local merge could take the new topology and map the streams to - * the existing local topology. The new topology stream format caps - * would be copied into the merged topology so we could change what - * codecs are negotiated. + * \internal + * \brief Remove declined streams appended beyond orig_topology. + * \since 15.0.0 + * + * \param sdp_state + * \param orig_topology Negotiated or initial topology. + * \param new_topology New proposed topology. + * + * \return Nothing */ +static void remove_appended_declined_streams(const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *orig_topology, + struct ast_stream_topology *new_topology) +{ + struct ast_stream *stream; + int orig_count; + int idx; + + orig_count = ast_stream_topology_get_count(orig_topology); + for (idx = ast_stream_topology_get_count(new_topology); orig_count < idx;) { + --idx; + stream = ast_stream_topology_get_stream(new_topology, idx); + if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) { + continue; + } + ast_stream_topology_del_stream(new_topology, idx); + } +} + +/*! + * \internal + * \brief Setup a new state stream from a possibly existing state stream. + * \since 15.0.0 + * + * \param sdp_state + * \param new_state_stream What state stream to setup + * \param old_state_stream Source of previous state stream information. + * May be NULL. + * \param new_type Type of the new state stream. + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int setup_new_stream_capabilities( + const struct ast_sdp_state *sdp_state, + struct sdp_state_stream *new_state_stream, + struct sdp_state_stream *old_state_stream, + enum ast_media_type new_type) +{ + if (old_state_stream) { + /* + * Copy everything potentially useful for a new stream state type + * from the old stream of a possible different type. + */ + sdp_state_stream_copy_common(new_state_stream, old_state_stream); + /* We also need to preserve the locally_held state for the new stream. */ + new_state_stream->locally_held = old_state_stream->locally_held; + } + new_state_stream->type = new_type; + + switch (new_type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + new_state_stream->rtp = create_rtp(sdp_state->options, new_type); + if (!new_state_stream->rtp) { + return -1; + } + break; + case AST_MEDIA_TYPE_IMAGE: + new_state_stream->udptl = create_udptl(sdp_state->options); + if (!new_state_stream->udptl) { + return -1; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + return 0; +} + /*! - * \brief Merge existing stream capabilities and a new topology into joint capabilities. + * \brief Merge existing stream capabilities and a new topology. * * \param sdp_state The state needing capabilities merged - * \param new_topology The new topology to base merged capabilities on - * \param is_local If new_topology is a local update. + * \param new_topology The topology to merge with our proposed capabilities * * \details + * * This is a bit complicated. The idea is that we already have some * capabilities set, and we've now been confronted with a new stream - * topology. We want to take what's been presented to us and merge - * those new capabilities with our own. - * - * For each of the new streams, we try to find a corresponding stream - * in our proposed capabilities. If we find one, then we get the - * compatible formats of the two streams and create a new stream with - * those formats set. We then will re-use the underlying media - * instance (such as an RTP instance) on this merged stream. - * - * The is_local parameter determines whether we should attempt to - * create new media instances. If we do not find a corresponding - * stream, then we create a new one. If the is_local parameter is - * true, this created stream is made a clone of the new stream, and a - * media instance is created. If the is_local parameter is not true, - * then the created stream has no formats set and no media instance is - * created for it. + * topology from the system. We want to take what we had before and + * merge them with the new topology from the system. + * + * According to the RFC, stream slots can change their types only if + * they are carrying the same logical information or an offer is + * reusing a declined slot or new stream slots are added to the end + * of the list. Switching a stream from audio to T.38 makes sense + * because the stream slot is carrying the same information just in a + * different format. + * + * We can setup new streams offered by the system up to our + * configured maximum stream slots. New stream slots requested over + * the maximum are discarded. + * + * \retval NULL An error occurred + * \retval non-NULL The merged capabilities + */ +static struct sdp_state_capabilities *merge_local_capabilities( + const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *new_topology) +{ + const struct sdp_state_capabilities *current = sdp_state->proposed_capabilities; + struct sdp_state_capabilities *merged_capabilities; + int idx; + + ast_assert(current != NULL); + + merged_capabilities = ast_calloc(1, sizeof(*merged_capabilities)); + if (!merged_capabilities) { + return NULL; + } + + merged_capabilities->topology = merge_local_topologies(sdp_state, current->topology, + new_topology, 1); + if (!merged_capabilities->topology) { + goto fail; + } + sdp_state_cb_offerer_modify_topology(sdp_state, merged_capabilities->topology); + remove_appended_declined_streams(sdp_state, current->topology, + merged_capabilities->topology); + + if (AST_VECTOR_INIT(&merged_capabilities->streams, + ast_stream_topology_get_count(merged_capabilities->topology))) { + goto fail; + } + + for (idx = 0; idx < ast_stream_topology_get_count(merged_capabilities->topology); ++idx) { + struct sdp_state_stream *merged_state_stream; + struct sdp_state_stream *current_state_stream; + struct ast_stream *merged_stream; + struct ast_stream *current_stream; + enum ast_media_type merged_stream_type; + enum ast_media_type current_stream_type; + + merged_state_stream = ast_calloc(1, sizeof(*merged_state_stream)); + if (!merged_state_stream) { + goto fail; + } + + merged_stream = ast_stream_topology_get_stream(merged_capabilities->topology, idx); + merged_stream_type = ast_stream_get_type(merged_stream); + + if (idx < ast_stream_topology_get_count(current->topology)) { + current_state_stream = AST_VECTOR_GET(¤t->streams, idx); + current_stream = ast_stream_topology_get_stream(current->topology, idx); + current_stream_type = ast_stream_get_type(current_stream); + } else { + /* The merged topology is adding a stream */ + current_state_stream = NULL; + current_stream = NULL; + current_stream_type = AST_MEDIA_TYPE_UNKNOWN; + } + + if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) { + if (current_state_stream) { + /* Copy everything potentially useful to a declined stream state. */ + sdp_state_stream_copy_common(merged_state_stream, current_state_stream); + } + merged_state_stream->type = merged_stream_type; + } else if (!current_stream + || ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED) { + /* This is a new stream */ + if (setup_new_stream_capabilities(sdp_state, merged_state_stream, + current_state_stream, merged_stream_type)) { + sdp_state_stream_free(merged_state_stream); + goto fail; + } + } else if (merged_stream_type == current_stream_type) { + /* Stream type is not changing. */ + sdp_state_stream_copy(merged_state_stream, current_state_stream); + } else { + /* + * Stream type is changing. Need to replace the stream. + * + * Unsupported streams should already be handled earlier because + * they are always declined. + */ + ast_assert(sdp_is_stream_type_supported(merged_stream_type)); + + /* + * XXX We might need to keep the old RTP instance if the new + * stream type is also RTP. We would just be changing between + * audio and video in that case. However we will create a new + * RTP instance anyway since its purpose has to be changing. + * Any RTP packets in flight from the old stream type might + * cause mischief. + */ + if (setup_new_stream_capabilities(sdp_state, merged_state_stream, + current_state_stream, merged_stream_type)) { + sdp_state_stream_free(merged_state_stream); + goto fail; + } + } + + if (AST_VECTOR_APPEND(&merged_capabilities->streams, merged_state_stream)) { + sdp_state_stream_free(merged_state_stream); + goto fail; + } + } + + return merged_capabilities; + +fail: + sdp_state_capabilities_free(merged_capabilities); + return NULL; +} + +static void merge_remote_stream_capabilities( + const struct ast_sdp_state *sdp_state, + struct sdp_state_stream *joint_state_stream, + struct sdp_state_stream *local_state_stream, + struct ast_stream *remote_stream) +{ + struct ast_rtp_codecs *codecs; + + *joint_state_stream = *local_state_stream; + /* + * Need to explicitly set the type to the remote because we could + * be changing the type between audio and video. + */ + joint_state_stream->type = ast_stream_get_type(remote_stream); + + switch (joint_state_stream->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + ao2_bump(joint_state_stream->rtp); + codecs = ast_stream_get_data(remote_stream, AST_STREAM_DATA_RTP_CODECS); + ast_assert(codecs != NULL); + if (sdp_state->role == SDP_ROLE_ANSWERER) { + /* + * Setup rx payload type mapping to prefer the mapping + * from the peer that the RFC says we SHOULD use. + */ + ast_rtp_codecs_payloads_xover(codecs, codecs, NULL); + } + ast_rtp_codecs_payloads_copy(codecs, + ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance), + joint_state_stream->rtp->instance); + break; + case AST_MEDIA_TYPE_IMAGE: + joint_state_stream->udptl = ao2_bump(joint_state_stream->udptl); + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } +} + +static int create_remote_stream_capabilities( + const struct ast_sdp_state *sdp_state, + struct sdp_state_stream *joint_state_stream, + struct sdp_state_stream *local_state_stream, + struct ast_stream *remote_stream) +{ + struct ast_rtp_codecs *codecs; + + /* We can only create streams if we are the answerer */ + ast_assert(sdp_state->role == SDP_ROLE_ANSWERER); + + if (local_state_stream) { + /* + * Copy everything potentially useful for a new stream state type + * from the old stream of a possible different type. + */ + sdp_state_stream_copy_common(joint_state_stream, local_state_stream); + /* We also need to preserve the locally_held state for the new stream. */ + joint_state_stream->locally_held = local_state_stream->locally_held; + } + joint_state_stream->type = ast_stream_get_type(remote_stream); + + switch (joint_state_stream->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + joint_state_stream->rtp = create_rtp(sdp_state->options, joint_state_stream->type); + if (!joint_state_stream->rtp) { + return -1; + } + + /* + * Setup rx payload type mapping to prefer the mapping + * from the peer that the RFC says we SHOULD use. + */ + codecs = ast_stream_get_data(remote_stream, AST_STREAM_DATA_RTP_CODECS); + ast_assert(codecs != NULL); + ast_rtp_codecs_payloads_xover(codecs, codecs, NULL); + ast_rtp_codecs_payloads_copy(codecs, + ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance), + joint_state_stream->rtp->instance); + break; + case AST_MEDIA_TYPE_IMAGE: + joint_state_stream->udptl = create_udptl(sdp_state->options); + if (!joint_state_stream->udptl) { + return -1; + } + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + return 0; +} + +/*! + * \internal + * \brief Create a joint topology from the remote topology. + * \since 15.0.0 + * + * \param sdp_state The state needing capabilities merged. + * \param local Capabilities to merge the remote topology into. + * \param remote_topology The topology to merge with our local capabilities. + * + * \retval joint topology on success. + * \retval NULL on failure. + */ +static struct ast_stream_topology *merge_remote_topology( + const struct ast_sdp_state *sdp_state, + const struct sdp_state_capabilities *local, + const struct ast_stream_topology *remote_topology) +{ + struct ast_stream_topology *joint_topology; + int idx; + + joint_topology = ast_stream_topology_alloc(); + if (!joint_topology) { + return NULL; + } + + for (idx = 0; idx < ast_stream_topology_get_count(remote_topology); ++idx) { + enum ast_media_type local_stream_type; + enum ast_media_type remote_stream_type; + struct ast_stream *remote_stream; + struct ast_stream *local_stream; + struct ast_stream *joint_stream; + struct sdp_state_stream *local_state_stream; + + remote_stream = ast_stream_topology_get_stream(remote_topology, idx); + remote_stream_type = ast_stream_get_type(remote_stream); + + if (idx < ast_stream_topology_get_count(local->topology)) { + local_state_stream = AST_VECTOR_GET(&local->streams, idx); + local_stream = ast_stream_topology_get_stream(local->topology, idx); + local_stream_type = ast_stream_get_type(local_stream); + } else { + /* The remote is adding a stream slot */ + local_state_stream = NULL; + local_stream = NULL; + local_stream_type = AST_MEDIA_TYPE_UNKNOWN; + + if (sdp_state->role != SDP_ROLE_ANSWERER) { + /* Remote cannot add a new stream slot in an answer SDP */ + ast_debug(1, + "Bad. Ignoring new %s stream slot remote answer SDP trying to add.\n", + ast_codec_media_type2str(remote_stream_type)); + continue; + } + } + + if (local_stream + && ast_stream_get_state(local_stream) != AST_STREAM_STATE_REMOVED) { + if (remote_stream_type == local_stream_type) { + /* Stream type is not changing. */ + joint_stream = merge_remote_stream(sdp_state, local_stream, + local_state_stream->locally_held, remote_stream); + } else if (sdp_state->role == SDP_ROLE_ANSWERER) { + /* Stream type is changing. */ + joint_stream = merge_remote_stream(sdp_state, NULL, + local_state_stream->locally_held, remote_stream); + } else { + /* + * Remote cannot change the stream type we offered. + * Mark as declined. + */ + ast_debug(1, + "Bad. Remote answer SDP trying to change the stream type from %s to %s.\n", + ast_codec_media_type2str(local_stream_type), + ast_codec_media_type2str(remote_stream_type)); + joint_stream = decline_stream(local_stream_type, + ast_stream_get_name(local_stream)); + } + } else { + /* Local stream is either dead/declined or nonexistent. */ + if (sdp_state->role == SDP_ROLE_ANSWERER) { + if (sdp_is_stream_type_supported(remote_stream_type) + && ast_stream_get_state(remote_stream) != AST_STREAM_STATE_REMOVED + && idx < ast_sdp_options_get_max_streams(sdp_state->options)) { + /* Try to create the new stream */ + joint_stream = merge_remote_stream(sdp_state, NULL, + local_state_stream ? local_state_stream->locally_held : 0, + remote_stream); + } else { + const char *stream_name; + + /* Decline the remote stream. */ + if (local_stream + && local_stream_type == remote_stream_type) { + /* Preserve the previous stream name */ + stream_name = ast_stream_get_name(local_stream); + } else { + stream_name = NULL; + } + joint_stream = decline_stream(remote_stream_type, stream_name); + } + } else { + /* Decline the stream. */ + if (DEBUG_ATLEAST(1) + && ast_stream_get_state(remote_stream) != AST_STREAM_STATE_REMOVED) { + /* + * Remote cannot request a new stream in place of a declined + * stream in an answer SDP. + */ + ast_log(LOG_DEBUG, + "Bad. Remote answer SDP trying to use a declined stream slot for %s.\n", + ast_codec_media_type2str(remote_stream_type)); + } + joint_stream = decline_stream(local_stream_type, + ast_stream_get_name(local_stream)); + } + } + + if (!joint_stream) { + goto fail; + } + if (ast_stream_topology_append_stream(joint_topology, joint_stream) < 0) { + ast_stream_free(joint_stream); + goto fail; + } + } + + return joint_topology; + +fail: + ast_stream_topology_free(joint_topology); + return NULL; +} + +/*! + * \brief Merge our stream capabilities and a remote topology into joint capabilities. + * + * \param sdp_state The state needing capabilities merged + * \param remote_topology The topology to merge with our proposed capabilities + * + * \details + * This is a bit complicated. The idea is that we already have some + * capabilities set, and we've now been confronted with a stream + * topology from the remote end. We want to take what's been + * presented to us and merge those new capabilities with our own. + * + * According to the RFC, stream slots can change their types only if + * they are carrying the same logical information or an offer is + * reusing a declined slot or new stream slots are added to the end + * of the list. Switching a stream from audio to T.38 makes sense + * because the stream slot is carrying the same information just in a + * different format. + * + * When we are the answerer we can setup new streams offered by the + * remote up to our configured maximum stream slots. New stream + * slots offered over the maximum are unconditionally declined. * * \retval NULL An error occurred * \retval non-NULL The merged capabilities */ -static struct sdp_state_capabilities *merge_capabilities(const struct ast_sdp_state *sdp_state, - const struct ast_stream_topology *new_topology, int is_local) +static struct sdp_state_capabilities *merge_remote_capabilities( + const struct ast_sdp_state *sdp_state, + const struct ast_stream_topology *remote_topology) { const struct sdp_state_capabilities *local = sdp_state->proposed_capabilities; struct sdp_state_capabilities *joint_capabilities; - int media_indices[AST_MEDIA_TYPE_END] = {0}; - int i; - static const char dummy_name[] = "dummy"; + int idx; ast_assert(local != NULL); @@ -700,150 +1975,131 @@ static struct sdp_state_capabilities *merge_capabilities(const struct ast_sdp_st return NULL; } - joint_capabilities->topology = ast_stream_topology_alloc(); + joint_capabilities->topology = merge_remote_topology(sdp_state, local, remote_topology); if (!joint_capabilities->topology) { goto fail; } - if (AST_VECTOR_INIT(&joint_capabilities->streams, AST_VECTOR_SIZE(&local->streams))) { + if (sdp_state->role == SDP_ROLE_ANSWERER) { + sdp_state_cb_answerer_modify_topology(sdp_state, joint_capabilities->topology); + } + idx = ast_stream_topology_get_count(joint_capabilities->topology); + if (AST_VECTOR_INIT(&joint_capabilities->streams, idx)) { goto fail; } - ast_sockaddr_copy(&joint_capabilities->connection_address, &local->connection_address); - for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) { - enum ast_media_type new_stream_type; - struct ast_stream *new_stream; + for (idx = 0; idx < ast_stream_topology_get_count(remote_topology); ++idx) { + enum ast_media_type local_stream_type; + enum ast_media_type remote_stream_type; + struct ast_stream *remote_stream; struct ast_stream *local_stream; struct ast_stream *joint_stream; + struct sdp_state_stream *local_state_stream; struct sdp_state_stream *joint_state_stream; - int local_index; joint_state_stream = ast_calloc(1, sizeof(*joint_state_stream)); if (!joint_state_stream) { goto fail; } - new_stream = ast_stream_topology_get_stream(new_topology, i); - new_stream_type = ast_stream_get_type(new_stream); + remote_stream = ast_stream_topology_get_stream(remote_topology, idx); + remote_stream_type = ast_stream_get_type(remote_stream); - local_index = get_corresponding_index(local->topology, new_stream_type, media_indices); - if (0 <= local_index) { - local_stream = ast_stream_topology_get_stream(local->topology, local_index); - if (!strcmp(ast_stream_get_name(local_stream), dummy_name)) { - /* The local stream is a non-exixtent dummy stream. */ - local_stream = NULL; - } + if (idx < ast_stream_topology_get_count(local->topology)) { + local_state_stream = AST_VECTOR_GET(&local->streams, idx); + local_stream = ast_stream_topology_get_stream(local->topology, idx); + local_stream_type = ast_stream_get_type(local_stream); } else { + /* The remote is adding a stream slot */ + local_state_stream = NULL; local_stream = NULL; - } - if (local_stream) { - struct sdp_state_stream *local_state_stream; - struct ast_rtp_codecs *codecs; + local_stream_type = AST_MEDIA_TYPE_UNKNOWN; - if (is_local) { - /* Replace the local stream with the new local stream. */ - joint_stream = ast_stream_clone(new_stream, NULL); - } else { - joint_stream = merge_streams(local_stream, new_stream); - } - if (!joint_stream) { + if (sdp_state->role != SDP_ROLE_ANSWERER) { + /* Remote cannot add a new stream slot in an answer SDP */ sdp_state_stream_free(joint_state_stream); - goto fail; - } - - local_state_stream = AST_VECTOR_GET(&local->streams, local_index); - joint_state_stream->type = local_state_stream->type; - - switch (joint_state_stream->type) { - case AST_MEDIA_TYPE_AUDIO: - case AST_MEDIA_TYPE_VIDEO: - joint_state_stream->rtp = ao2_bump(local_state_stream->rtp); - if (is_local) { - break; - } - codecs = ast_stream_get_data(new_stream, AST_STREAM_DATA_RTP_CODECS); - ast_assert(codecs != NULL); - if (sdp_state->role == SDP_ROLE_ANSWERER) { - /* - * Setup rx payload type mapping to prefer the mapping - * from the peer that the RFC says we SHOULD use. - */ - ast_rtp_codecs_payloads_xover(codecs, codecs, NULL); - } - ast_rtp_codecs_payloads_copy(codecs, - ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance), - joint_state_stream->rtp->instance); - break; - case AST_MEDIA_TYPE_IMAGE: - joint_state_stream->udptl = ao2_bump(local_state_stream->udptl); - joint_state_stream->t38_local_params = local_state_stream->t38_local_params; - break; - case AST_MEDIA_TYPE_UNKNOWN: - case AST_MEDIA_TYPE_TEXT: - case AST_MEDIA_TYPE_END: break; } + } - if (!ast_sockaddr_isnull(&local_state_stream->connection_address)) { - ast_sockaddr_copy(&joint_state_stream->connection_address, - &local_state_stream->connection_address); + joint_stream = ast_stream_topology_get_stream(joint_capabilities->topology, + idx); + + if (local_stream + && ast_stream_get_state(local_stream) != AST_STREAM_STATE_REMOVED) { + if (ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED) { + /* Copy everything potentially useful to a declined stream state. */ + sdp_state_stream_copy_common(joint_state_stream, local_state_stream); + + joint_state_stream->type = ast_stream_get_type(joint_stream); + } else if (remote_stream_type == local_stream_type) { + /* Stream type is not changing. */ + merge_remote_stream_capabilities(sdp_state, joint_state_stream, + local_state_stream, remote_stream); + ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream)); } else { - ast_sockaddr_setnull(&joint_state_stream->connection_address); - } - joint_state_stream->locally_held = local_state_stream->locally_held; - } else if (is_local) { - /* We don't have a stream state that corresponds to the stream in the new topology, so - * create a stream state as appropriate. - */ - joint_stream = ast_stream_clone(new_stream, NULL); - if (!joint_stream) { - sdp_state_stream_free(joint_state_stream); - goto fail; - } - - switch (new_stream_type) { - case AST_MEDIA_TYPE_AUDIO: - case AST_MEDIA_TYPE_VIDEO: - joint_state_stream->rtp = create_rtp(sdp_state->options, - new_stream_type); - if (!joint_state_stream->rtp) { - ast_stream_free(joint_stream); + /* + * Stream type is changing. Need to replace the stream. + * + * XXX We might need to keep the old RTP instance if the new + * stream type is also RTP. We would just be changing between + * audio and video in that case. However we will create a new + * RTP instance anyway since its purpose has to be changing. + * Any RTP packets in flight from the old stream type might + * cause mischief. + */ + if (create_remote_stream_capabilities(sdp_state, joint_state_stream, + local_state_stream, remote_stream)) { sdp_state_stream_free(joint_state_stream); goto fail; } - break; - case AST_MEDIA_TYPE_IMAGE: - joint_state_stream->udptl = create_udptl(sdp_state->options); - if (!joint_state_stream->udptl) { - ast_stream_free(joint_stream); - sdp_state_stream_free(joint_state_stream); - goto fail; - } - break; - case AST_MEDIA_TYPE_UNKNOWN: - case AST_MEDIA_TYPE_TEXT: - case AST_MEDIA_TYPE_END: - break; + ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream)); } - ast_sockaddr_setnull(&joint_state_stream->connection_address); - joint_state_stream->locally_held = 0; } else { - /* We don't have a stream that corresponds to the stream in the new topology. Create a - * dummy stream to go in its place so that the resulting SDP created will contain - * the stream but will have no port or codecs set - */ - joint_stream = ast_stream_alloc(dummy_name, new_stream_type); - if (!joint_stream) { - sdp_state_stream_free(joint_state_stream); - goto fail; + /* Local stream is either dead/declined or nonexistent. */ + if (sdp_state->role == SDP_ROLE_ANSWERER) { + if (ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED) { + if (local_state_stream) { + /* Copy everything potentially useful to a declined stream state. */ + sdp_state_stream_copy_common(joint_state_stream, local_state_stream); + } + joint_state_stream->type = ast_stream_get_type(joint_stream); + } else { + /* Try to create the new stream */ + if (create_remote_stream_capabilities(sdp_state, joint_state_stream, + local_state_stream, remote_stream)) { + sdp_state_stream_free(joint_state_stream); + goto fail; + } + ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream)); + } + } else { + /* Decline the stream. */ + ast_assert(ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED); + if (local_state_stream) { + /* Copy everything potentially useful to a declined stream state. */ + sdp_state_stream_copy_common(joint_state_stream, local_state_stream); + } + joint_state_stream->type = ast_stream_get_type(joint_stream); } } - if (ast_stream_topology_append_stream(joint_capabilities->topology, joint_stream) < 0) { - ast_stream_free(joint_stream); - sdp_state_stream_free(joint_state_stream); - goto fail; + /* Determine if the remote placed the stream on hold. */ + joint_state_stream->remotely_held = 0; + if (ast_stream_get_state(joint_stream) != AST_STREAM_STATE_REMOVED) { + enum ast_stream_state remote_state; + + remote_state = ast_stream_get_state(remote_stream); + switch (remote_state) { + case AST_STREAM_STATE_INACTIVE: + case AST_STREAM_STATE_SENDONLY: + joint_state_stream->remotely_held = 1; + break; + default: + break; + } } + if (AST_VECTOR_APPEND(&joint_capabilities->streams, joint_state_stream)) { sdp_state_stream_free(joint_state_stream); goto fail; @@ -998,11 +2254,6 @@ static void update_rtp_after_merge(const struct ast_sdp_state *state, struct ast_sdp_c_line *c_line; struct ast_sockaddr *addrs; - if (!rtp) { - /* This is a dummy stream */ - return; - } - c_line = remote_m_line->c_line; if (!c_line) { c_line = remote_sdp->c_line; @@ -1058,11 +2309,6 @@ static void update_udptl_after_merge(const struct ast_sdp_state *state, struct s unsigned int fax_max_datagram; struct ast_sockaddr *addrs; - if (!udptl) { - /* This is a dummy stream */ - return; - } - a_line = ast_sdp_m_find_attribute(remote_m_line, "t38faxmaxdatagram", -1); if (!a_line) { a_line = ast_sdp_m_find_attribute(remote_m_line, "t38maxdatagram", -1); @@ -1102,6 +2348,49 @@ static void update_udptl_after_merge(const struct ast_sdp_state *state, struct s } } +static void sdp_apply_negotiated_state(struct ast_sdp_state *sdp_state) +{ + struct sdp_state_capabilities *capabilities = sdp_state->negotiated_capabilities; + int idx; + + if (!capabilities) { + /* Nothing to apply */ + return; + } + + sdp_state_cb_preapply_topology(sdp_state, capabilities->topology); + for (idx = 0; idx < AST_VECTOR_SIZE(&capabilities->streams); ++idx) { + struct sdp_state_stream *state_stream; + struct ast_stream *stream; + + stream = ast_stream_topology_get_stream(capabilities->topology, idx); + if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) { + /* Stream is declined */ + continue; + } + + state_stream = AST_VECTOR_GET(&capabilities->streams, idx); + switch (ast_stream_get_type(stream)) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + update_rtp_after_merge(sdp_state, state_stream->rtp, sdp_state->options, + sdp_state->remote_sdp, ast_sdp_get_m(sdp_state->remote_sdp, idx)); + break; + case AST_MEDIA_TYPE_IMAGE: + update_udptl_after_merge(sdp_state, state_stream->udptl, sdp_state->options, + sdp_state->remote_sdp, ast_sdp_get_m(sdp_state->remote_sdp, idx)); + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + /* All unsupported streams are declined */ + ast_assert(0); + break; + } + } + sdp_state_cb_postapply_topology(sdp_state, capabilities->topology); +} + static void set_negotiated_capabilities(struct ast_sdp_state *sdp_state, struct sdp_state_capabilities *new_capabilities) { @@ -1120,6 +2409,81 @@ static void set_proposed_capabilities(struct ast_sdp_state *sdp_state, sdp_state_capabilities_free(old_capabilities); } +/*! + * \internal + * \brief Copy the new capabilities into the proposed capabilities. + * \since 15.0.0 + * + * \param sdp_state The current SDP state + * \param new_capabilities Capabilities to copy + * + * \retval 0 on success. + * \retval -1 on failure. + */ +static int update_proposed_capabilities(struct ast_sdp_state *sdp_state, + struct sdp_state_capabilities *new_capabilities) +{ + struct sdp_state_capabilities *proposed_capabilities; + int idx; + + proposed_capabilities = ast_calloc(1, sizeof(*proposed_capabilities)); + if (!proposed_capabilities) { + return -1; + } + + proposed_capabilities->topology = ast_stream_topology_clone(new_capabilities->topology); + if (!proposed_capabilities->topology) { + goto fail; + } + + if (AST_VECTOR_INIT(&proposed_capabilities->streams, + AST_VECTOR_SIZE(&new_capabilities->streams))) { + goto fail; + } + + for (idx = 0; idx < AST_VECTOR_SIZE(&new_capabilities->streams); ++idx) { + struct sdp_state_stream *proposed_state_stream; + struct sdp_state_stream *new_state_stream; + + proposed_state_stream = ast_calloc(1, sizeof(*proposed_state_stream)); + if (!proposed_state_stream) { + goto fail; + } + + new_state_stream = AST_VECTOR_GET(&new_capabilities->streams, idx); + *proposed_state_stream = *new_state_stream; + + switch (proposed_state_stream->type) { + case AST_MEDIA_TYPE_AUDIO: + case AST_MEDIA_TYPE_VIDEO: + ao2_bump(proposed_state_stream->rtp); + break; + case AST_MEDIA_TYPE_IMAGE: + ao2_bump(proposed_state_stream->udptl); + break; + case AST_MEDIA_TYPE_UNKNOWN: + case AST_MEDIA_TYPE_TEXT: + case AST_MEDIA_TYPE_END: + break; + } + + /* This is explicitly never set on the proposed capabilities struct */ + proposed_state_stream->remotely_held = 0; + + if (AST_VECTOR_APPEND(&proposed_capabilities->streams, proposed_state_stream)) { + sdp_state_stream_free(proposed_state_stream); + goto fail; + } + } + + set_proposed_capabilities(sdp_state, proposed_capabilities); + return 0; + +fail: + sdp_state_capabilities_free(proposed_capabilities); + return -1; +} + static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_state, const struct sdp_state_capabilities *capabilities); @@ -1127,65 +2491,47 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta * \brief Merge SDPs into a joint SDP. * * This function is used to take a remote SDP and merge it with our local - * capabilities to produce a new local SDP. After creating the new local SDP, - * it then iterates through media instances and updates them as necessary. For + * capabilities to produce a new local SDP. After creating the new local SDP, + * it then iterates through media instances and updates them as necessary. For * instance, if a specific RTP feature is supported by both us and the far end, * then we can ensure that the feature is enabled. * * \param sdp_state The current SDP state - * \retval -1 Failure + * * \retval 0 Success + * \retval -1 Failure + * Use ast_sdp_state_is_offer_rejected() to see if the offer SDP was rejected. */ static int merge_sdps(struct ast_sdp_state *sdp_state, const struct ast_sdp *remote_sdp) { struct sdp_state_capabilities *joint_capabilities; struct ast_stream_topology *remote_capabilities; - int i; remote_capabilities = ast_get_topology_from_sdp(remote_sdp, - sdp_state->options->g726_non_standard); + ast_sdp_options_get_g726_non_standard(sdp_state->options)); if (!remote_capabilities) { return -1; } - joint_capabilities = merge_capabilities(sdp_state, remote_capabilities, 0); + joint_capabilities = merge_remote_capabilities(sdp_state, remote_capabilities); ast_stream_topology_free(remote_capabilities); if (!joint_capabilities) { return -1; } - set_negotiated_capabilities(sdp_state, joint_capabilities); - - if (sdp_state->local_sdp) { - ast_sdp_free(sdp_state->local_sdp); - sdp_state->local_sdp = NULL; - } - - sdp_state->local_sdp = sdp_create_from_state(sdp_state, joint_capabilities); - if (!sdp_state->local_sdp) { - return -1; + if (sdp_state->role == SDP_ROLE_ANSWERER) { + sdp_state->remote_offer_rejected = + sdp_topology_is_rejected(joint_capabilities->topology) ? 1 : 0; + if (sdp_state->remote_offer_rejected) { + sdp_state_capabilities_free(joint_capabilities); + return -1; + } } + set_negotiated_capabilities(sdp_state, joint_capabilities); - for (i = 0; i < AST_VECTOR_SIZE(&joint_capabilities->streams); ++i) { - struct sdp_state_stream *state_stream; - - state_stream = AST_VECTOR_GET(&joint_capabilities->streams, i); + ao2_cleanup(sdp_state->remote_sdp); + sdp_state->remote_sdp = ao2_bump((struct ast_sdp *) remote_sdp); - switch (ast_stream_get_type(ast_stream_topology_get_stream(joint_capabilities->topology, i))) { - case AST_MEDIA_TYPE_AUDIO: - case AST_MEDIA_TYPE_VIDEO: - update_rtp_after_merge(sdp_state, state_stream->rtp, sdp_state->options, - remote_sdp, ast_sdp_get_m(remote_sdp, i)); - break; - case AST_MEDIA_TYPE_IMAGE: - update_udptl_after_merge(sdp_state, state_stream->udptl, sdp_state->options, - remote_sdp, ast_sdp_get_m(remote_sdp, i)); - break; - case AST_MEDIA_TYPE_UNKNOWN: - case AST_MEDIA_TYPE_TEXT: - case AST_MEDIA_TYPE_END: - break; - } - } + sdp_apply_negotiated_state(sdp_state); return 0; } @@ -1194,10 +2540,43 @@ const struct ast_sdp *ast_sdp_state_get_local_sdp(struct ast_sdp_state *sdp_stat { ast_assert(sdp_state != NULL); - if (sdp_state->role == SDP_ROLE_NOT_SET) { + switch (sdp_state->role) { + case SDP_ROLE_NOT_SET: ast_assert(sdp_state->local_sdp == NULL); sdp_state->role = SDP_ROLE_OFFERER; + + if (sdp_state->pending_topology_update) { + struct sdp_state_capabilities *capabilities; + + /* We have a topology update to perform before generating the offer */ + capabilities = merge_local_capabilities(sdp_state, + sdp_state->pending_topology_update); + if (!capabilities) { + break; + } + ast_stream_topology_free(sdp_state->pending_topology_update); + sdp_state->pending_topology_update = NULL; + set_proposed_capabilities(sdp_state, capabilities); + } + + /* + * Allow the system to configure the topology streams + * before we create the offer SDP. + */ + sdp_state_cb_offerer_config_topology(sdp_state, + sdp_state->proposed_capabilities->topology); + sdp_state->local_sdp = sdp_create_from_state(sdp_state, sdp_state->proposed_capabilities); + break; + case SDP_ROLE_OFFERER: + break; + case SDP_ROLE_ANSWERER: + if (!sdp_state->local_sdp + && sdp_state->negotiated_capabilities + && !sdp_state->remote_offer_rejected) { + sdp_state->local_sdp = sdp_create_from_state(sdp_state, sdp_state->negotiated_capabilities); + } + break; } return sdp_state->local_sdp; @@ -1237,35 +2616,63 @@ int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, cons return -1; } ret = ast_sdp_state_set_remote_sdp(sdp_state, sdp); - ast_sdp_free(sdp); + ao2_ref(sdp, -1); return ret; } -int ast_sdp_state_reset(struct ast_sdp_state *sdp_state) +int ast_sdp_state_is_offer_rejected(struct ast_sdp_state *sdp_state) +{ + return sdp_state->remote_offer_rejected; +} + +int ast_sdp_state_is_offerer(struct ast_sdp_state *sdp_state) +{ + return sdp_state->role == SDP_ROLE_OFFERER; +} + +int ast_sdp_state_is_answerer(struct ast_sdp_state *sdp_state) +{ + return sdp_state->role == SDP_ROLE_ANSWERER; +} + +int ast_sdp_state_restart_negotiations(struct ast_sdp_state *sdp_state) { ast_assert(sdp_state != NULL); - ast_sdp_free(sdp_state->local_sdp); + ao2_cleanup(sdp_state->local_sdp); sdp_state->local_sdp = NULL; - set_proposed_capabilities(sdp_state, NULL); - sdp_state->role = SDP_ROLE_NOT_SET; + sdp_state->remote_offer_rejected = 0; + + if (sdp_state->negotiated_capabilities) { + update_proposed_capabilities(sdp_state, sdp_state->negotiated_capabilities); + } return 0; } -int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *streams) +int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *topology) { - struct sdp_state_capabilities *capabilities; + struct ast_stream_topology *merged_topology; + ast_assert(sdp_state != NULL); - ast_assert(streams != NULL); + ast_assert(topology != NULL); - capabilities = merge_capabilities(sdp_state, streams, 1); - if (!capabilities) { - return -1; + if (sdp_state->pending_topology_update) { + merged_topology = merge_local_topologies(sdp_state, + sdp_state->pending_topology_update, topology, 0); + if (!merged_topology) { + return -1; + } + ast_stream_topology_free(sdp_state->pending_topology_update); + sdp_state->pending_topology_update = merged_topology; + } else { + sdp_state->pending_topology_update = ast_stream_topology_clone(topology); + if (!sdp_state->pending_topology_update) { + return -1; + } } - set_proposed_capabilities(sdp_state, capabilities); return 0; } @@ -1275,9 +2682,9 @@ void ast_sdp_state_set_local_address(struct ast_sdp_state *sdp_state, struct ast ast_assert(sdp_state != NULL); if (!address) { - ast_sockaddr_setnull(&sdp_state->proposed_capabilities->connection_address); + ast_sockaddr_setnull(&sdp_state->connection_address); } else { - ast_sockaddr_copy(&sdp_state->proposed_capabilities->connection_address, address); + ast_sockaddr_copy(&sdp_state->connection_address, address); } } @@ -1301,18 +2708,37 @@ int ast_sdp_state_set_connection_address(struct ast_sdp_state *sdp_state, int st return 0; } +void ast_sdp_state_set_global_locally_held(struct ast_sdp_state *sdp_state, unsigned int locally_held) +{ + ast_assert(sdp_state != NULL); + + sdp_state->locally_held = locally_held ? 1 : 0; +} + +unsigned int ast_sdp_state_get_global_locally_held(const struct ast_sdp_state *sdp_state) +{ + ast_assert(sdp_state != NULL); + + return sdp_state->locally_held; +} + void ast_sdp_state_set_locally_held(struct ast_sdp_state *sdp_state, int stream_index, unsigned int locally_held) { struct sdp_state_stream *stream_state; ast_assert(sdp_state != NULL); - stream_state = sdp_state_get_stream(sdp_state, stream_index); - if (!stream_state) { - return; + locally_held = locally_held ? 1 : 0; + + stream_state = sdp_state_get_joint_stream(sdp_state, stream_index); + if (stream_state) { + stream_state->locally_held = locally_held; } - stream_state->locally_held = locally_held; + stream_state = sdp_state_get_stream(sdp_state, stream_index); + if (stream_state) { + stream_state->locally_held = locally_held; + } } unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state, @@ -1321,7 +2747,7 @@ unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_stat struct sdp_state_stream *stream_state; ast_assert(sdp_state != NULL); - stream_state = sdp_state_get_stream(sdp_state, stream_index); + stream_state = sdp_state_get_joint_stream(sdp_state, stream_index); if (!stream_state) { return 0; } @@ -1329,6 +2755,21 @@ unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_stat return stream_state->locally_held; } +unsigned int ast_sdp_state_get_remotely_held(const struct ast_sdp_state *sdp_state, + int stream_index) +{ + struct sdp_state_stream *stream_state; + + ast_assert(sdp_state != NULL); + + stream_state = sdp_state_get_joint_stream(sdp_state, stream_index); + if (!stream_state) { + return 0; + } + + return stream_state->remotely_held; +} + void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state, int stream_index, struct ast_control_t38_parameters *params) { @@ -1336,11 +2777,9 @@ void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state, ast_assert(sdp_state != NULL && params != NULL); stream_state = sdp_state_get_stream(sdp_state, stream_index); - if (!stream_state) { - return; + if (stream_state) { + stream_state->t38_local_params = *params; } - - stream_state->t38_local_params = *params; } /*! @@ -1398,7 +2837,8 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s caps = ast_stream_get_formats(stream); stream_state = AST_VECTOR_GET(&capabilities->streams, stream_index); - if (stream_state->rtp && caps && ast_format_cap_count(caps)) { + if (stream_state->rtp && caps && ast_format_cap_count(caps) + && AST_STREAM_STATE_REMOVED != ast_stream_get_state(stream)) { rtp = stream_state->rtp->instance; } else { /* This is a disabled stream */ @@ -1408,7 +2848,7 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s if (rtp) { struct ast_sockaddr address_rtp; - if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_rtp)) { + if (sdp_state_stream_get_connection_address(sdp_state, stream_state, &address_rtp)) { return -1; } rtp_port = ast_sockaddr_port(&address_rtp); @@ -1426,6 +2866,8 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s } if (rtp_port) { + const char *direction; + /* Stream is not declined/disabled */ for (i = 0; i < ast_format_cap_count(caps); i++) { struct ast_format *format = ast_format_cap_get_format(caps, i); @@ -1502,12 +2944,27 @@ static int sdp_add_m_from_rtp_stream(struct ast_sdp *sdp, const struct ast_sdp_s } } - a_line = ast_sdp_a_alloc(ast_sdp_state_get_locally_held(sdp_state, stream_index) - ? "sendonly" : "sendrecv", ""); - if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { - ast_sdp_a_free(a_line); - ast_sdp_m_free(m_line); - return -1; + if (sdp_state->locally_held || stream_state->locally_held) { + if (stream_state->remotely_held) { + direction = "inactive"; + } else { + direction = "sendonly"; + } + } else { + if (stream_state->remotely_held) { + direction = "recvonly"; + } else { + /* Default is "sendrecv" */ + direction = NULL; + } + } + if (direction) { + a_line = ast_sdp_a_alloc(direction, ""); + if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { + ast_sdp_a_free(a_line); + ast_sdp_m_free(m_line); + return -1; + } } add_ssrc_attributes(m_line, options, rtp); @@ -1585,7 +3042,8 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp ast_assert(sdp && options && stream); stream_state = AST_VECTOR_GET(&capabilities->streams, stream_index); - if (stream_state->udptl) { + if (stream_state->udptl + && AST_STREAM_STATE_REMOVED != ast_stream_get_state(stream)) { udptl = stream_state->udptl; } else { /* This is a disabled stream */ @@ -1595,7 +3053,7 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp if (udptl) { struct ast_sockaddr address_udptl; - if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_udptl)) { + if (sdp_state_stream_get_connection_address(sdp_state, stream_state, &address_udptl)) { return -1; } udptl_port = ast_sockaddr_port(&address_udptl); @@ -1619,8 +3077,6 @@ static int sdp_add_m_from_udptl_stream(struct ast_sdp *sdp, const struct ast_sdp if (udptl_port) { /* Stream is not declined/disabled */ - stream_state = sdp_state_get_stream(sdp_state, stream_index); - snprintf(tmp, sizeof(tmp), "%u", stream_state->t38_local_params.version); a_line = ast_sdp_a_alloc("T38FaxVersion", tmp); if (!a_line || ast_sdp_m_add_a(m_line, a_line)) { @@ -1773,7 +3229,6 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta } stream_count = ast_stream_topology_get_count(topology); - for (stream_num = 0; stream_num < stream_count; stream_num++) { switch (ast_stream_get_type(ast_stream_topology_get_stream(topology, stream_num))) { case AST_MEDIA_TYPE_AUDIO: @@ -1798,7 +3253,7 @@ static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_sta error: if (sdp) { - ast_sdp_free(sdp); + ao2_ref(sdp, -1); } else { ast_sdp_t_free(t_line); ast_sdp_s_free(s_line); diff --git a/main/stream.c b/main/stream.c index 093cd5450..89ed0dc53 100644 --- a/main/stream.c +++ b/main/stream.c @@ -214,6 +214,23 @@ const char *ast_stream_state2str(enum ast_stream_state state) } } +enum ast_stream_state ast_stream_str2state(const char *str) +{ + if (!strcmp("sendrecv", str)) { + return AST_STREAM_STATE_SENDRECV; + } + if (!strcmp("sendonly", str)) { + return AST_STREAM_STATE_SENDONLY; + } + if (!strcmp("recvonly", str)) { + return AST_STREAM_STATE_RECVONLY; + } + if (!strcmp("inactive", str)) { + return AST_STREAM_STATE_INACTIVE; + } + return AST_STREAM_STATE_REMOVED; +} + void *ast_stream_get_data(struct ast_stream *stream, enum ast_stream_data_slot slot) { ast_assert(stream != NULL); |