diff options
-rw-r--r-- | include/asterisk/sdp.h | 12 | ||||
-rw-r--r-- | include/asterisk/sdp_options.h | 224 | ||||
-rw-r--r-- | include/asterisk/sdp_state.h | 127 | ||||
-rw-r--r-- | include/asterisk/stream.h | 11 | ||||
-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 | ||||
-rw-r--r-- | res/res_sdp_translator_pjmedia.c | 4 | ||||
-rw-r--r-- | tests/test_sdp.c | 1150 |
11 files changed, 3347 insertions, 559 deletions
diff --git a/include/asterisk/sdp.h b/include/asterisk/sdp.h index 224a0e5a3..768469544 100644 --- a/include/asterisk/sdp.h +++ b/include/asterisk/sdp.h @@ -254,16 +254,6 @@ void ast_sdp_s_free(struct ast_sdp_s_line *s_line); void ast_sdp_t_free(struct ast_sdp_t_line *t_line); /*! - * \brief Free an SDP - * Frees the sdp and all resources it contains - * - * \param sdp The sdp to free - * - * \since 15 - */ -void ast_sdp_free(struct ast_sdp *sdp); - -/*! * \brief Allocate an SDP Attribute * * \param name Attribute Name @@ -544,7 +534,7 @@ int ast_sdp_m_add_format(struct ast_sdp_m_line *m_line, const struct ast_sdp_opt int rtp_code, int asterisk_format, const struct ast_format *format, int code); /*! - * \brief Create an SDP + * \brief Create an SDP ao2 object * * \param o_line Origin * \param c_line Connection diff --git a/include/asterisk/sdp_options.h b/include/asterisk/sdp_options.h index b8c1bbd56..e45ae8cb1 100644 --- a/include/asterisk/sdp_options.h +++ b/include/asterisk/sdp_options.h @@ -20,6 +20,7 @@ #define _ASTERISK_SDP_OPTIONS_H #include "asterisk/udptl.h" +#include "asterisk/format_cap.h" struct ast_sdp_options; @@ -80,6 +81,149 @@ enum ast_sdp_options_encryption { }; /*! + * \brief Callback when processing an offer SDP for our answer SDP. + * \since 15.0.0 + * + * \details + * This callback is called after merging our last negotiated topology + * with the remote's offer topology and before we have sent our answer + * SDP. At this point you can alter new_topology streams. You can + * decline, remove formats, or rename streams. Changing anything else + * on the streams is likely to not end well. + * + * * To decline a stream simply set the stream state to + * AST_STREAM_STATE_REMOVED. You could implement a maximum number + * of active streams of a given type policy. + * + * * To remove formats use the format API to remove any formats from a + * stream. The streams have the current joint negotiated formats. + * Most likely you would want to remove all but the first format. + * + * * To rename a stream you need to clone the stream and give it a + * new name and then set it in new_topology using + * ast_stream_topology_set_stream(). + * + * \note Removing all formats is an error. You should decline the + * stream instead. + * + * \param context User supplied context data pointer for the SDP + * state. + * \param old_topology Active negotiated topology. NULL if this is + * the first SDP negotiation. The old topology is available so you + * can tell if any streams are new or changing type. + * \param new_topology New negotiated topology that we intend to + * generate the answer SDP. + * + * \return Nothing + */ +typedef void (*ast_sdp_answerer_modify_cb)(void *context, + const struct ast_stream_topology *old_topology, + struct ast_stream_topology *new_topology); + +/*! + * \internal + * \brief Callback when generating a topology for our SDP offer. + * \since 15.0.0 + * + * \details + * This callback is called after merging any topology updates from the + * system by ast_sdp_state_update_local_topology() and before we have + * sent our offer SDP. At this point you can alter new_topology + * streams. You can decline, add/remove/update formats, or rename + * streams. Changing anything else on the streams is likely to not + * end well. + * + * * To decline a stream simply set the stream state to + * AST_STREAM_STATE_REMOVED. You could implement a maximum number + * of active streams of a given type policy. + * + * * To update formats use the format API to change formats of the + * streams. The streams have the current proposed formats. You + * could do whatever you want for formats but you should stay within + * the configured formats for the stream type's endpoint. However, + * you should use ast_sdp_state_update_local_topology() instead of + * this backdoor method. + * + * * To rename a stream you need to clone the stream and give it a + * new name and then set it in new_topology using + * ast_stream_topology_set_stream(). + * + * \note Removing all formats is an error. You should decline the + * stream instead. + * + * \note Declined new streams that are in slots higher than present in + * old_topology are removed so the SDP can be smaller. The remote has + * never seen those slots so we shouldn't bother keeping them. + * + * \param context User supplied context data pointer for the SDP + * state. + * \param old_topology Active negotiated topology. NULL if this is + * the first SDP negotiation. The old topology is available so you + * can tell if any streams are new or changing type. + * \param new_topology Merged topology that we intend to generate the + * offer SDP. + * + * \return Nothing + */ +typedef void (*ast_sdp_offerer_modify_cb)(void *context, + const struct ast_stream_topology *old_topology, + struct ast_stream_topology *new_topology); + +/*! + * \brief Callback when generating an offer SDP to configure extra stream data. + * \since 15.0.0 + * + * \details + * This callback is called after any ast_sdp_offerer_modify_cb + * callback and before we have sent our offer SDP. The callback can + * call several SDP API calls to configure the proposed capabilities + * of streams before we create the SDP offer. For example, the + * callback could configure a stream specific connection address, T.38 + * parameters, RTP instance, or UDPTL instance parameters. + * + * \param context User supplied context data pointer for the SDP + * state. + * \param topology Topology ready to configure extra stream options. + * + * \return Nothing + */ +typedef void (*ast_sdp_offerer_config_cb)(void *context, const struct ast_stream_topology *topology); + +/*! + * \brief Callback before applying a topology. + * \since 15.0.0 + * + * \details + * This callback is called before the topology is applied so the + * using module can do what is necessary before the topology becomes + * active. + * + * \param context User supplied context data pointer for the SDP + * state. + * \param topology Topology ready to be applied. + * + * \return Nothing + */ +typedef void (*ast_sdp_preapply_cb)(void *context, const struct ast_stream_topology *topology); + +/*! + * \brief Callback after applying a topology. + * \since 15.0.0 + * + * \details + * This callback is called after the topology is applied so the + * using module can do what is necessary after the topology becomes + * active. + * + * \param context User supplied context data pointer for the SDP + * state. + * \param topology Topology already applied. + * + * \return Nothing + */ +typedef void (*ast_sdp_postapply_cb)(void *context, const struct ast_stream_topology *topology); + +/*! * \since 15.0.0 * \brief Allocate a new SDP options structure. * @@ -204,6 +348,24 @@ void ast_sdp_options_set_rtp_engine(struct ast_sdp_options *options, */ const char *ast_sdp_options_get_rtp_engine(const struct ast_sdp_options *options); +void ast_sdp_options_set_state_context(struct ast_sdp_options *options, void *state_context); +void *ast_sdp_options_get_state_context(const struct ast_sdp_options *options); + +void ast_sdp_options_set_answerer_modify_cb(struct ast_sdp_options *options, ast_sdp_answerer_modify_cb answerer_modify_cb); +ast_sdp_answerer_modify_cb ast_sdp_options_get_answerer_modify_cb(const struct ast_sdp_options *options); + +void ast_sdp_options_set_offerer_modify_cb(struct ast_sdp_options *options, ast_sdp_offerer_modify_cb offerer_modify_cb); +ast_sdp_offerer_modify_cb ast_sdp_options_get_offerer_modify_cb(const struct ast_sdp_options *options); + +void ast_sdp_options_set_offerer_config_cb(struct ast_sdp_options *options, ast_sdp_offerer_config_cb offerer_config_cb); +ast_sdp_offerer_config_cb ast_sdp_options_get_offerer_config_cb(const struct ast_sdp_options *options); + +void ast_sdp_options_set_preapply_cb(struct ast_sdp_options *options, ast_sdp_preapply_cb preapply_cb); +ast_sdp_preapply_cb ast_sdp_options_get_preapply_cb(const struct ast_sdp_options *options); + +void ast_sdp_options_set_postapply_cb(struct ast_sdp_options *options, ast_sdp_postapply_cb postapply_cb); +ast_sdp_postapply_cb ast_sdp_options_get_postapply_cb(const struct ast_sdp_options *options); + /*! * \since 15.0.0 * \brief Set SDP Options rtp_symmetric @@ -505,6 +667,26 @@ unsigned int ast_sdp_options_get_udptl_far_max_datagram(const struct ast_sdp_opt /*! * \since 15.0.0 + * \brief Set SDP Options max_streams + * + * \param options SDP Options + * \param max_streams + */ +void ast_sdp_options_set_max_streams(struct ast_sdp_options *options, + unsigned int max_streams); + +/*! + * \since 15.0.0 + * \brief Get SDP Options max_streams + * + * \param options SDP Options + * + * \returns max_streams + */ +unsigned int ast_sdp_options_get_max_streams(const struct ast_sdp_options *options); + +/*! + * \since 15.0.0 * \brief Enable setting SSRC level attributes on SDPs * * \param options SDP Options @@ -547,4 +729,46 @@ void ast_sdp_options_set_sched_type(struct ast_sdp_options *options, struct ast_sched_context *ast_sdp_options_get_sched_type(const struct ast_sdp_options *options, enum ast_media_type type); +/*! + * \brief Set all allowed stream types to create new streams. + * \since 15.0.0 + * + * \param options SDP Options + * \param cap Format capabilities to set all allowed stream types at once. + * Could be NULL to disable creating any new streams. + * + * \return Nothing + */ +void ast_sdp_options_set_format_caps(struct ast_sdp_options *options, + struct ast_format_cap *cap); + +/*! + * \brief Set the SDP options format cap used to create new streams of the type. + * \since 15.0.0 + * + * \param options SDP Options + * \param type Media type the format cap represents. + * \param cap Format capabilities to use for the specified media type. + * Could be NULL to disable creating new streams of type. + * + * \return Nothing + */ +void ast_sdp_options_set_format_cap_type(struct ast_sdp_options *options, + enum ast_media_type type, struct ast_format_cap *cap); + +/*! + * \brief Get the SDP options format cap used to create new streams of the type. + * \since 15.0.0 + * + * \param options SDP Options + * \param type Media type the format cap represents. + * + * \retval NULL if stream not allowed to be created. + * \retval cap to use in negotiating the new stream. + * + * \note The returned cap does not have its own ao2 ref. + */ +struct ast_format_cap *ast_sdp_options_get_format_cap_type(const struct ast_sdp_options *options, + enum ast_media_type type); + #endif /* _ASTERISK_SDP_OPTIONS_H */ diff --git a/include/asterisk/sdp_state.h b/include/asterisk/sdp_state.h index b8209e1d5..ec9d502e2 100644 --- a/include/asterisk/sdp_state.h +++ b/include/asterisk/sdp_state.h @@ -30,12 +30,22 @@ struct ast_control_t38_parameters; /*! * \brief Allocate a new SDP state * + * \details * SDP state keeps tabs on everything SDP-related for a media session. * Most SDP operations will require the state to be provided. * Ownership of the SDP options is taken on by the SDP state. * A good strategy is to call this during session creation. + * + * \param topology Initial stream topology to offer. + * NULL if we are going to be the answerer. We can always + * update the local topology later if it turns out we need + * to be the offerer. + * \param options SDP options for the duration of the session. + * + * \retval SDP state struct + * \retval NULL on failure */ -struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams, +struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *topology, struct ast_sdp_options *options); /*! @@ -86,6 +96,8 @@ int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_ * * If this is called prior to receiving a remote SDP, then this will just mirror * the local configured endpoint capabilities. + * + * \note Cannot return NULL. It is a BUG if it does. */ const struct ast_stream_topology *ast_sdp_state_get_joint_topology( const struct ast_sdp_state *sdp_state); @@ -93,6 +105,7 @@ const struct ast_stream_topology *ast_sdp_state_get_joint_topology( /*! * \brief Get the local topology * + * \note Cannot return NULL. It is a BUG if it does. */ const struct ast_stream_topology *ast_sdp_state_get_local_topology( const struct ast_sdp_state *sdp_state); @@ -114,9 +127,10 @@ const struct ast_sdp_options *ast_sdp_state_get_options( * \retval NULL Failure * * \note - * This function will allocate a new SDP with RTP instances if it has not already - * been allocated. - * + * This function will return the last local SDP created if one were + * previously requested for the current negotiation. Otherwise it + * creates our SDP offer/answer depending on what role we are playing + * in the current negotiation. */ const struct ast_sdp *ast_sdp_state_get_local_sdp(struct ast_sdp_state *sdp_state); @@ -152,6 +166,7 @@ const void *ast_sdp_state_get_local_sdp_impl(struct ast_sdp_state *sdp_state); * * \retval 0 Success * \retval non-0 Failure + * Use ast_sdp_state_is_offer_rejected() to see if the SDP offer was rejected. * * \since 15 */ @@ -165,39 +180,72 @@ int ast_sdp_state_set_remote_sdp(struct ast_sdp_state *sdp_state, const struct a * * \retval 0 Success * \retval non-0 Failure + * Use ast_sdp_state_is_offer_rejected() to see if the SDP offer was rejected. * * \since 15 */ int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, const void *remote); /*! - * \brief Reset the SDP state and stream capabilities as if the SDP state had just been allocated. + * \brief Was the set remote offer rejected. + * \since 15.0.0 * * \param sdp_state - * \param remote The implementation's representation of an SDP. * - * \retval 0 Success + * \retval 0 if not rejected. + * \retval non-zero if rejected. + */ +int ast_sdp_state_is_offer_rejected(struct ast_sdp_state *sdp_state); + +/*! + * \brief Are we the SDP offerer. + * \since 15.0.0 * - * \note - * This is most useful for when a channel driver is sending a session refresh message - * and needs to re-advertise its initial capabilities instead of the previously-negotiated - * joint capabilities. + * \param sdp_state * - * \since 15 + * \retval 0 if we are not the offerer. + * \retval non-zero we are the offerer. */ -int ast_sdp_state_reset(struct ast_sdp_state *sdp_state); +int ast_sdp_state_is_offerer(struct ast_sdp_state *sdp_state); + +/*! + * \brief Are we the SDP answerer. + * \since 15.0.0 + * + * \param sdp_state + * + * \retval 0 if we are not the answerer. + * \retval non-zero we are the answerer. + */ +int ast_sdp_state_is_answerer(struct ast_sdp_state *sdp_state); + +/*! + * \brief Restart the SDP offer/answer negotiations. + * + * \param sdp_state + * + * \retval 0 Success + * \retval non-0 Failure + */ +int ast_sdp_state_restart_negotiations(struct ast_sdp_state *sdp_state); /*! * \brief Update the local stream topology on the SDP state. * + * \details + * Basically we are saving off any topology updates until we create the + * next SDP offer. Repeated updates merge with the previous updated + * topology. + * * \param sdp_state - * \param streams The new stream topology. + * \param topology The new stream topology. * * \retval 0 Success + * \retval non-0 Failure * * \since 15 */ -int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *streams); +int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *topology); /*! * \brief Set the local address (IP address) to use for connection addresses @@ -231,7 +279,26 @@ int ast_sdp_state_set_connection_address(struct ast_sdp_state *sdp_state, int st /*! * \since 15.0.0 - * \brief Set a stream to be held or unheld + * \brief Set the global locally held state. + * + * \param sdp_state + * \param locally_held + */ +void ast_sdp_state_set_global_locally_held(struct ast_sdp_state *sdp_state, unsigned int locally_held); + +/*! + * \since 15.0.0 + * \brief Get the global locally held state. + * + * \param sdp_state + * + * \returns locally_held + */ +unsigned int ast_sdp_state_get_global_locally_held(const struct ast_sdp_state *sdp_state); + +/*! + * \since 15.0.0 + * \brief Set a stream to be held or unheld locally * * \param sdp_state * \param stream_index The stream to set the held value for @@ -242,25 +309,37 @@ void ast_sdp_state_set_locally_held(struct ast_sdp_state *sdp_state, /*! * \since 15.0.0 - * \brief Set the UDPTL session parameters + * \brief Get whether a stream is locally held or not * * \param sdp_state - * \param stream_index The stream to set the UDPTL session parameters for - * \param params + * \param stream_index The stream to get the held state for + * + * \returns locally_held */ -void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state, - int stream_index, struct ast_control_t38_parameters *params); +unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state, + int stream_index); /*! * \since 15.0.0 - * \brief Get whether a stream is held or not + * \brief Get whether a stream is remotely held or not * * \param sdp_state * \param stream_index The stream to get the held state for * - * \returns locally_held + * \returns remotely_held */ -unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state, +unsigned int ast_sdp_state_get_remotely_held(const struct ast_sdp_state *sdp_state, int stream_index); +/*! + * \since 15.0.0 + * \brief Set the UDPTL session parameters + * + * \param sdp_state + * \param stream_index The stream to set the UDPTL session parameters for + * \param params + */ +void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state, + int stream_index, struct ast_control_t38_parameters *params); + #endif /* _ASTERISK_SDP_STATE_H */ diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h index 4027231ed..2c1053a7b 100644 --- a/include/asterisk/stream.h +++ b/include/asterisk/stream.h @@ -228,6 +228,17 @@ void ast_stream_set_state(struct ast_stream *stream, enum ast_stream_state state const char *ast_stream_state2str(enum ast_stream_state state); /*! + * \brief Convert a string to a stream state + * + * \param str The string to convert + * + * \return The stream state + * + * \since 15.0.0 + */ +enum ast_stream_state ast_stream_str2state(const char *str); + +/*! * \brief Get the opaque stream data * * \param stream The media stream 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); diff --git a/res/res_sdp_translator_pjmedia.c b/res/res_sdp_translator_pjmedia.c index 85f246e83..d80f3d554 100644 --- a/res/res_sdp_translator_pjmedia.c +++ b/res/res_sdp_translator_pjmedia.c @@ -484,7 +484,7 @@ AST_TEST_DEFINE(pjmedia_to_sdp_test) } cleanup: - ast_sdp_free(sdp); + ao2_cleanup(sdp); ast_sdp_translator_free(translator); pj_pool_release(pool); return res; @@ -560,7 +560,7 @@ AST_TEST_DEFINE(sdp_to_pjmedia_test) } cleanup: - ast_sdp_free(sdp); + ao2_cleanup(sdp); ast_sdp_translator_free(translator); pj_pool_release(pool); return res; diff --git a/tests/test_sdp.c b/tests/test_sdp.c index 7eef3f741..662e2aaf1 100644 --- a/tests/test_sdp.c +++ b/tests/test_sdp.c @@ -89,12 +89,31 @@ static int validate_m_line(struct ast_test *test, const struct ast_sdp_m_line *m } if (ast_sdp_m_get_payload_count(m_line) != num_payloads) { - ast_test_status_update(test, "Expected m-line payload count %d but got %d\n", - num_payloads, ast_sdp_m_get_payload_count(m_line)); + ast_test_status_update(test, "Expected %s m-line payload count %d but got %d\n", + media_type, num_payloads, ast_sdp_m_get_payload_count(m_line)); return -1; } - ast_test_status_update(test, "SDP m-line is as expected\n"); + ast_test_status_update(test, "SDP %s m-line is as expected\n", media_type); + return 0; +} + +static int validate_m_line_declined(struct ast_test *test, + const struct ast_sdp_m_line *m_line, const char *media_type) +{ + if (strcmp(m_line->type, media_type)) { + ast_test_status_update(test, "Expected m-line media type %s but got %s\n", + media_type, m_line->type); + return -1; + } + + if (m_line->port != 0) { + ast_test_status_update(test, "Expected %s m-line to be declined but got port %u\n", + media_type, m_line->port); + return -1; + } + + ast_test_status_update(test, "SDP %s m-line is as expected\n", media_type); return 0; } @@ -438,6 +457,26 @@ struct sdp_format { const char *formats; }; +static int build_sdp_option_formats(struct ast_sdp_options *options, int num_streams, const struct sdp_format *formats) +{ + int idx; + + for (idx = 0; idx < num_streams; ++idx) { + RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup); + + caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!caps) { + return -1; + } + + if (ast_format_cap_update_by_allow_disallow(caps, formats[idx].formats, 1) < 0) { + return -1; + } + ast_sdp_options_set_format_cap_type(options, formats[idx].type, caps); + } + return 0; +} + /*! * \brief Common method to build an SDP state for a test. * @@ -450,9 +489,16 @@ struct sdp_format { * * \param num_streams The number of elements in the formats array. * \param formats Array of media types and formats that will be in the state. + * \param opt_num_streams The number of new stream types allowed to create. + * Not used if test_options provided. + * \param opt_formats Array of new stream media types and formats allowed to create. + * NULL if use a default stream creation. + * Not used if test_options provided. * \param test_options Optional SDP options. */ -static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats, struct ast_sdp_options *test_options) +static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats, + int opt_num_streams, const struct sdp_format *opt_formats, + struct ast_sdp_options *test_options) { struct ast_stream_topology *topology = NULL; struct ast_sdp_state *state = NULL; @@ -460,10 +506,34 @@ static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_f int i; if (!test_options) { + unsigned int max_streams; + + static const struct sdp_format sdp_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_VIDEO, "vp8" }, + { AST_MEDIA_TYPE_IMAGE, "t38" }, + }; + options = sdp_options_common(); if (!options) { goto end; } + + /* Determine max_streams to allow */ + max_streams = ARRAY_LEN(sdp_formats); + if (ARRAY_LEN(sdp_formats) < num_streams) { + max_streams = num_streams; + } + ast_sdp_options_set_max_streams(options, max_streams); + + /* Determine new stream formats and types allowed */ + if (!opt_formats) { + opt_num_streams = ARRAY_LEN(sdp_formats); + opt_formats = sdp_formats; + } + if (build_sdp_option_formats(options, opt_num_streams, opt_formats)) { + goto end; + } } else { options = test_options; } @@ -489,7 +559,10 @@ static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_f goto end; } ast_stream_set_formats(stream, caps); - ast_stream_topology_append_stream(topology, stream); + if (ast_stream_topology_append_stream(topology, stream) < 0) { + ast_stream_free(stream); + goto end; + } } state = ast_sdp_state_alloc(topology, options); @@ -530,7 +603,8 @@ AST_TEST_DEFINE(topology_to_sdp) break; } - sdp_state = build_sdp_state(ARRAY_LEN(formats), formats, NULL); + sdp_state = build_sdp_state(ARRAY_LEN(formats), formats, + ARRAY_LEN(formats), formats, NULL); if (!sdp_state) { goto end; } @@ -674,7 +748,8 @@ AST_TEST_DEFINE(sdp_to_topology) break; } - sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats, NULL); + sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats, + ARRAY_LEN(sdp_formats), sdp_formats, NULL); if (!sdp_state) { res = AST_TEST_FAIL; goto end; @@ -723,7 +798,7 @@ end: return res; } -static int validate_merged_sdp(struct ast_test *test, const struct ast_sdp *sdp) +static int validate_avi_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp) { struct ast_sdp_m_line *m_line; @@ -769,7 +844,11 @@ static int validate_merged_sdp(struct ast_test *test, const struct ast_sdp *sdp) return 0; } -AST_TEST_DEFINE(sdp_merge_symmetric) +static enum ast_test_result_state sdp_negotiation_completed_tests(struct ast_test *test, + int offer_num_streams, const struct sdp_format *offer_formats, + int answer_num_streams, const struct sdp_format *answer_formats, + int allowed_ans_num_streams, const struct sdp_format *allowed_ans_formats, + int (*validate_sdp)(struct ast_test *test, const struct ast_sdp *sdp)) { enum ast_test_result_state res = AST_TEST_PASS; struct ast_sdp_state *sdp_state_offerer = NULL; @@ -777,38 +856,15 @@ AST_TEST_DEFINE(sdp_merge_symmetric) const struct ast_sdp *offerer_sdp; const struct ast_sdp *answerer_sdp; - static const struct sdp_format offerer_formats[] = { - { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" }, - { AST_MEDIA_TYPE_VIDEO, "h264,vp8" }, - { AST_MEDIA_TYPE_IMAGE, "t38" }, - }; - static const struct sdp_format answerer_formats[] = { - { AST_MEDIA_TYPE_AUDIO, "ulaw" }, - { AST_MEDIA_TYPE_VIDEO, "vp8" }, - { AST_MEDIA_TYPE_IMAGE, "t38" }, - }; - - switch(cmd) { - case TEST_INIT: - info->name = "sdp_merge_symmetric"; - info->category = "/main/sdp/"; - info->summary = "Merge two SDPs with symmetric stream types"; - info->description = - "SDPs 1 and 2 each have one audio and one video stream (in that order).\n" - "SDP 1 offers to SDP 2, who answers. We ensure that both local SDPs have\n" - "the expected stream types and the expected formats"; - return AST_TEST_NOT_RUN; - case TEST_EXECUTE: - break; - } - - sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL); + sdp_state_offerer = build_sdp_state(offer_num_streams, offer_formats, + offer_num_streams, offer_formats, NULL); if (!sdp_state_offerer) { res = AST_TEST_FAIL; goto end; } - sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL); + sdp_state_answerer = build_sdp_state(answer_num_streams, answer_formats, + allowed_ans_num_streams, allowed_ans_formats, NULL); if (!sdp_state_answerer) { res = AST_TEST_FAIL; goto end; @@ -820,22 +876,37 @@ AST_TEST_DEFINE(sdp_merge_symmetric) goto end; } - ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp); + if (ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp)) { + res = AST_TEST_FAIL; + goto end; + } answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer); if (!answerer_sdp) { res = AST_TEST_FAIL; goto end; } - ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp); + if (ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp)) { + res = AST_TEST_FAIL; + goto end; + } - /* Get the offerer SDP again because it's now going to be the joint SDP */ + /* + * Restart SDP negotiations to build the joint SDP on the offerer + * side. Otherwise we will get the original offer for use in + * case of retransmissions. + */ + if (ast_sdp_state_restart_negotiations(sdp_state_offerer)) { + ast_test_status_update(test, "Restarting negotiations failed\n"); + res = AST_TEST_FAIL; + goto end; + } offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); - if (validate_merged_sdp(test, offerer_sdp)) { + if (validate_sdp(test, offerer_sdp)) { res = AST_TEST_FAIL; goto end; } - if (validate_merged_sdp(test, answerer_sdp)) { + if (validate_sdp(test, answerer_sdp)) { res = AST_TEST_FAIL; goto end; } @@ -847,14 +918,37 @@ end: return res; } -AST_TEST_DEFINE(sdp_merge_crisscross) +AST_TEST_DEFINE(sdp_negotiation_initial) { - enum ast_test_result_state res = AST_TEST_PASS; - struct ast_sdp_state *sdp_state_offerer = NULL; - struct ast_sdp_state *sdp_state_answerer = NULL; - const struct ast_sdp *offerer_sdp; - const struct ast_sdp *answerer_sdp; + static const struct sdp_format offerer_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" }, + { AST_MEDIA_TYPE_VIDEO, "h264,vp8" }, + { AST_MEDIA_TYPE_IMAGE, "t38" }, + }; + switch(cmd) { + case TEST_INIT: + info->name = "sdp_negotiation_initial"; + info->category = "/main/sdp/"; + info->summary = "Simulate an initial negotiation"; + info->description = + "Initial negotiation tests creating new streams on the answering side.\n" + "After negotiation both offerer and answerer sides should have the same\n" + "expected stream types and formats."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return sdp_negotiation_completed_tests(test, + ARRAY_LEN(offerer_formats), offerer_formats, + 0, NULL, + 0, NULL, + validate_avi_sdp_streams); +} + +AST_TEST_DEFINE(sdp_negotiation_type_change) +{ static const struct sdp_format offerer_formats[] = { { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" }, { AST_MEDIA_TYPE_VIDEO, "h264,vp8" }, @@ -868,174 +962,194 @@ AST_TEST_DEFINE(sdp_merge_crisscross) switch(cmd) { case TEST_INIT: - info->name = "sdp_merge_crisscross"; + info->name = "sdp_negotiation_type_change"; info->category = "/main/sdp/"; - info->summary = "Merge two SDPs with symmetric stream types"; + info->summary = "Simulate a re-negotiation changing stream types"; info->description = - "SDPs 1 and 2 each have one audio and one video stream. However, SDP 1 and\n" - "2 natively have the formats in a different order.\n" - "SDP 1 offers to SDP 2, who answers. We ensure that both local SDPs have\n" - "the expected stream types and the expected formats. Since SDP 1 was the\n" - "offerer, the format order on SDP 1 should determine the order of formats in the SDPs"; + "Reinvite negotiation tests changing stream types on the answering side.\n" + "After negotiation both offerer and answerer sides should have the same\n" + "expected stream types and formats."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } - sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL); - if (!sdp_state_offerer) { - res = AST_TEST_FAIL; - goto end; - } + return sdp_negotiation_completed_tests(test, + ARRAY_LEN(offerer_formats), offerer_formats, + ARRAY_LEN(answerer_formats), answerer_formats, + 0, NULL, + validate_avi_sdp_streams); +} - sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL); - if (!sdp_state_answerer) { - res = AST_TEST_FAIL; - goto end; - } +static int validate_ava_declined_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp) +{ + struct ast_sdp_m_line *m_line; - offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); - if (!offerer_sdp) { - res = AST_TEST_FAIL; - goto end; + if (!sdp) { + return -1; } - ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp); - answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer); - if (!answerer_sdp) { - res = AST_TEST_FAIL; - goto end; + m_line = ast_sdp_get_m(sdp, 0); + if (validate_m_line_declined(test, m_line, "audio")) { + return -1; } - ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp); + m_line = ast_sdp_get_m(sdp, 1); + if (validate_m_line_declined(test, m_line, "video")) { + return -1; + } - /* Get the offerer SDP again because it's now going to be the joint SDP */ - offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); - if (validate_merged_sdp(test, offerer_sdp)) { - res = AST_TEST_FAIL; - goto end; + m_line = ast_sdp_get_m(sdp, 2); + if (validate_m_line(test, m_line, "audio", 1)) { + return -1; } - if (validate_merged_sdp(test, answerer_sdp)) { - res = AST_TEST_FAIL; - goto end; + if (validate_rtpmap(test, m_line, "PCMU")) { + return -1; } -end: - ast_sdp_state_free(sdp_state_offerer); - ast_sdp_state_free(sdp_state_answerer); + /* The other audio formats should *NOT* be present */ + if (!validate_rtpmap(test, m_line, "PCMA")) { + return -1; + } - return res; + return 0; } -static int validate_merged_sdp_asymmetric(struct ast_test *test, const struct ast_sdp *sdp, int is_offer) +AST_TEST_DEFINE(sdp_negotiation_decline_incompatible) +{ + static const struct sdp_format offerer_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "alaw" }, + { AST_MEDIA_TYPE_VIDEO, "vp8" }, + { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw" }, + }; + static const struct sdp_format answerer_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + }; + + switch(cmd) { + case TEST_INIT: + info->name = "sdp_negotiation_decline_incompatible"; + info->category = "/main/sdp/"; + info->summary = "Simulate an initial negotiation declining streams"; + info->description = + "Initial negotiation tests declining incompatible streams on the answering side.\n" + "After negotiation both offerer and answerer sides should have the same\n" + "expected stream types and formats."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return sdp_negotiation_completed_tests(test, + ARRAY_LEN(offerer_formats), offerer_formats, + ARRAY_LEN(answerer_formats), answerer_formats, + ARRAY_LEN(answerer_formats), answerer_formats, + validate_ava_declined_sdp_streams); +} + +static int validate_aaaa_declined_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp) { struct ast_sdp_m_line *m_line; - const char *side = is_offer ? "Offer side" : "Answer side"; if (!sdp) { - ast_test_status_update(test, "%s does not have a SDP\n", side); return -1; } - /* Stream 0 */ m_line = ast_sdp_get_m(sdp, 0); if (validate_m_line(test, m_line, "audio", 1)) { return -1; } - if (!m_line->port) { - ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 0, "n't"); - return -1; - } if (validate_rtpmap(test, m_line, "PCMU")) { return -1; } - /* The other audio formats should *NOT* be present */ - if (!validate_rtpmap(test, m_line, "PCMA")) { - return -1; - } - if (!validate_rtpmap(test, m_line, "G722")) { - return -1; - } - if (!validate_rtpmap(test, m_line, "opus")) { - return -1; - } - - /* The remaining streams should be declined */ - - /* Stream 1 */ m_line = ast_sdp_get_m(sdp, 1); if (validate_m_line(test, m_line, "audio", 1)) { return -1; } - if (m_line->port) { - ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 1, ""); + if (validate_rtpmap(test, m_line, "PCMU")) { return -1; } - /* Stream 2 */ m_line = ast_sdp_get_m(sdp, 2); - if (validate_m_line(test, m_line, "video", 1)) { + if (validate_m_line(test, m_line, "audio", 1)) { return -1; } - if (m_line->port) { - ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 2, ""); + if (validate_rtpmap(test, m_line, "PCMU")) { return -1; } - /* Stream 3 */ m_line = ast_sdp_get_m(sdp, 3); - if (validate_m_line(test, m_line, "image", 1)) { - return -1; - } - if (m_line->port) { - ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 3, ""); + if (validate_m_line_declined(test, m_line, "audio")) { return -1; } return 0; } -AST_TEST_DEFINE(sdp_merge_asymmetric) +AST_TEST_DEFINE(sdp_negotiation_decline_max_streams) +{ + static const struct sdp_format offerer_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + }; + + switch(cmd) { + case TEST_INIT: + info->name = "sdp_negotiation_decline_max_streams"; + info->category = "/main/sdp/"; + info->summary = "Simulate an initial negotiation declining excessive streams"; + info->description = + "Initial negotiation tests declining too many streams on the answering side.\n" + "After negotiation both offerer and answerer sides should have the same\n" + "expected stream types and formats."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + return sdp_negotiation_completed_tests(test, + ARRAY_LEN(offerer_formats), offerer_formats, + 0, NULL, + 0, NULL, + validate_aaaa_declined_sdp_streams); +} + +AST_TEST_DEFINE(sdp_negotiation_not_acceptable) { enum ast_test_result_state res = AST_TEST_PASS; struct ast_sdp_state *sdp_state_offerer = NULL; struct ast_sdp_state *sdp_state_answerer = NULL; const struct ast_sdp *offerer_sdp; - const struct ast_sdp *answerer_sdp; static const struct sdp_format offerer_formats[] = { - { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" }, - { AST_MEDIA_TYPE_AUDIO, "ulaw" }, - { AST_MEDIA_TYPE_VIDEO, "h261" }, - { AST_MEDIA_TYPE_IMAGE, "t38" }, - }; - static const struct sdp_format answerer_formats[] = { - { AST_MEDIA_TYPE_AUDIO, "ulaw" }, + { AST_MEDIA_TYPE_AUDIO, "alaw" }, + { AST_MEDIA_TYPE_AUDIO, "alaw" }, }; switch(cmd) { case TEST_INIT: - info->name = "sdp_merge_asymmetric"; + info->name = "sdp_negotiation_not_acceptable"; info->category = "/main/sdp/"; - info->summary = "Merge two SDPs with an asymmetric number of streams"; + info->summary = "Simulate an initial negotiation declining all streams"; info->description = - "SDP 1 offers a four stream topology: Audio,Audio,Video,T.38\n" - "SDP 2 only has a single audio stream topology\n" - "We ensure that both local SDPs have the expected stream types and\n" - "the expected declined streams"; + "Initial negotiation tests declining all streams for a 488 on the answering side.\n" + "Negotiations should fail because there are no acceptable streams."; return AST_TEST_NOT_RUN; case TEST_EXECUTE: break; } - sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL); + sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, + ARRAY_LEN(offerer_formats), offerer_formats, NULL); if (!sdp_state_offerer) { res = AST_TEST_FAIL; goto end; } - sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL); + sdp_state_answerer = build_sdp_state(0, NULL, 0, NULL, NULL); if (!sdp_state_answerer) { res = AST_TEST_FAIL; goto end; @@ -1047,24 +1161,15 @@ AST_TEST_DEFINE(sdp_merge_asymmetric) goto end; } - ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp); - answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer); - if (!answerer_sdp) { + if (!ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp)) { + ast_test_status_update(test, "Bad. Setting remote SDP was successful.\n"); res = AST_TEST_FAIL; goto end; } - - ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp); - -#if defined(XXX_TODO_NEED_TO_HANDLE_DECLINED_STREAMS_ON_OFFER_SIDE) - /* Get the offerer SDP again because it's now going to be the joint SDP */ - offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer); - if (validate_merged_sdp_asymmetric(test, offerer_sdp, 1)) { - res = AST_TEST_FAIL; - } -#endif - if (validate_merged_sdp_asymmetric(test, answerer_sdp, 0)) { + if (!ast_sdp_state_is_offer_rejected(sdp_state_answerer)) { + ast_test_status_update(test, "Bad. Negotiation failed for some other reason.\n"); res = AST_TEST_FAIL; + goto end; } end: @@ -1135,9 +1240,12 @@ AST_TEST_DEFINE(sdp_ssrc_attributes) ast_test_status_update(test, "Failed to allocate SDP options\n"); goto end; } + if (build_sdp_option_formats(options, ARRAY_LEN(formats), formats)) { + goto end; + } ast_sdp_options_set_ssrc(options, 1); - test_state = build_sdp_state(ARRAY_LEN(formats), formats, options); + test_state = build_sdp_state(ARRAY_LEN(formats), formats, 0, NULL, options); if (!test_state) { ast_test_status_update(test, "Failed to create SDP state\n"); goto end; @@ -1179,6 +1287,726 @@ end: return res; } +struct sdp_topology_stream { + /*! Media stream type: audio, video, image */ + enum ast_media_type type; + /*! Media stream state: removed/declined, sendrecv */ + enum ast_stream_state state; + /*! Comma separated list of formats allowed on the stream. Can be NULL if stream is removed/declined. */ + const char *formats; + /*! Optional name of stream. NULL for default name. */ + const char *name; +}; + +struct sdp_update_test { + /*! Maximum number of streams. (0 if default) */ + int max_streams; + /*! Optional initial SDP state topology (NULL if not present) */ + const struct sdp_topology_stream * const *initial; + /*! Required first topology update */ + const struct sdp_topology_stream * const *update_1; + /*! Optional second topology update (NULL if not present) */ + const struct sdp_topology_stream * const *update_2; + /*! Expected topology to be offered */ + const struct sdp_topology_stream * const *expected; +}; + +static struct ast_stream_topology *build_update_topology(const struct sdp_topology_stream * const *spec) +{ + struct ast_stream_topology *topology; + const struct sdp_topology_stream *desc; + + topology = ast_stream_topology_alloc(); + if (!topology) { + return NULL; + } + + for (desc = *spec; desc; ++spec, desc = *spec) { + struct ast_stream *stream; + const char *name; + + name = desc->name ?: ast_codec_media_type2str(desc->type); + stream = ast_stream_alloc(name, desc->type); + if (!stream) { + goto fail; + } + ast_stream_set_state(stream, desc->state); + if (desc->formats) { + struct ast_format_cap *caps; + + caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT); + if (!caps) { + goto fail; + } + if (ast_format_cap_update_by_allow_disallow(caps, desc->formats, 1) < 0) { + ao2_ref(caps, -1); + goto fail; + } + ast_stream_set_formats(stream, caps); + ao2_ref(caps, -1); + } + if (ast_stream_topology_append_stream(topology, stream) < 0) { + ast_stream_free(stream); + goto fail; + } + } + return topology; + +fail: + ast_stream_topology_free(topology); + return NULL; +} + +static int cmp_update_topology(struct ast_test *test, + const struct ast_stream_topology *expected, const struct ast_stream_topology *merged) +{ + int status = 0; + int idx; + int max_streams; + struct ast_stream *exp_stream; + struct ast_stream *mrg_stream; + + idx = ast_stream_topology_get_count(expected); + max_streams = ast_stream_topology_get_count(merged); + if (idx != max_streams) { + ast_test_status_update(test, "Expected %d streams got %d streams\n", + idx, max_streams); + status = -1; + } + if (idx < max_streams) { + max_streams = idx; + } + + /* Compare common streams by position */ + for (idx = 0; idx < max_streams; ++idx) { + exp_stream = ast_stream_topology_get_stream(expected, idx); + mrg_stream = ast_stream_topology_get_stream(merged, idx); + + if (strcmp(ast_stream_get_name(exp_stream), ast_stream_get_name(mrg_stream))) { + ast_test_status_update(test, + "Stream %d: Expected stream name '%s' got stream name '%s'\n", + idx, + ast_stream_get_name(exp_stream), + ast_stream_get_name(mrg_stream)); + status = -1; + } + + if (ast_stream_get_state(exp_stream) != ast_stream_get_state(mrg_stream)) { + ast_test_status_update(test, + "Stream %d: Expected stream state '%s' got stream state '%s'\n", + idx, + ast_stream_state2str(ast_stream_get_state(exp_stream)), + ast_stream_state2str(ast_stream_get_state(mrg_stream))); + status = -1; + } + + if (ast_stream_get_type(exp_stream) != ast_stream_get_type(mrg_stream)) { + ast_test_status_update(test, + "Stream %d: Expected stream type '%s' got stream type '%s'\n", + idx, + ast_codec_media_type2str(ast_stream_get_type(exp_stream)), + ast_codec_media_type2str(ast_stream_get_type(mrg_stream))); + status = -1; + continue; + } + + if (ast_stream_get_state(exp_stream) == AST_STREAM_STATE_REMOVED + || ast_stream_get_state(mrg_stream) == AST_STREAM_STATE_REMOVED) { + /* + * Cannot compare formats if one of the streams is + * declined because there may not be any on the declined + * stream. + */ + continue; + } + if (!ast_format_cap_identical(ast_stream_get_formats(exp_stream), + ast_stream_get_formats(mrg_stream))) { + ast_test_status_update(test, + "Stream %d: Expected formats do not match merged formats\n", + idx); + status = -1; + } + } + + return status; +} + + +static const struct sdp_topology_stream audio_declined_no_name = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_REMOVED, NULL, NULL +}; + +static const struct sdp_topology_stream audio_ulaw_no_name = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", NULL +}; + +static const struct sdp_topology_stream audio_alaw_no_name = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", NULL +}; + +static const struct sdp_topology_stream audio_g722_no_name = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", NULL +}; + +static const struct sdp_topology_stream audio_g723_no_name = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g723", NULL +}; + +static const struct sdp_topology_stream video_declined_no_name = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, NULL, NULL +}; + +static const struct sdp_topology_stream video_h261_no_name = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", NULL +}; + +static const struct sdp_topology_stream video_h263_no_name = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", NULL +}; + +static const struct sdp_topology_stream video_h264_no_name = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", NULL +}; + +static const struct sdp_topology_stream video_vp8_no_name = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "vp8", NULL +}; + +static const struct sdp_topology_stream image_declined_no_name = { + AST_MEDIA_TYPE_IMAGE, AST_STREAM_STATE_REMOVED, NULL, NULL +}; + +static const struct sdp_topology_stream image_t38_no_name = { + AST_MEDIA_TYPE_IMAGE, AST_STREAM_STATE_SENDRECV, "t38", NULL +}; + + +static const struct sdp_topology_stream *top_ulaw_alaw_h264__vp8[] = { + &audio_ulaw_no_name, + &audio_alaw_no_name, + &video_h264_no_name, + &video_vp8_no_name, + NULL +}; + +static const struct sdp_topology_stream *top__vp8_alaw_h264_ulaw[] = { + &video_vp8_no_name, + &audio_alaw_no_name, + &video_h264_no_name, + &audio_ulaw_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_alaw_ulaw__vp8_h264[] = { + &audio_alaw_no_name, + &audio_ulaw_no_name, + &video_vp8_no_name, + &video_h264_no_name, + NULL +}; + +/* Sorting by type with no new or deleted streams */ +static const struct sdp_update_test mrg_by_type_00 = { + .initial = top_ulaw_alaw_h264__vp8, + .update_1 = top__vp8_alaw_h264_ulaw, + .expected = top_alaw_ulaw__vp8_h264, +}; + + +static const struct sdp_topology_stream *top_alaw__vp8[] = { + &audio_alaw_no_name, + &video_vp8_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_h264__vp8_ulaw[] = { + &video_h264_no_name, + &video_vp8_no_name, + &audio_ulaw_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_ulaw_h264__vp8[] = { + &audio_ulaw_no_name, + &video_h264_no_name, + &video_vp8_no_name, + NULL +}; + +/* Sorting by type and adding a stream */ +static const struct sdp_update_test mrg_by_type_01 = { + .initial = top_alaw__vp8, + .update_1 = top_h264__vp8_ulaw, + .expected = top_ulaw_h264__vp8, +}; + + +static const struct sdp_topology_stream *top_alaw__vp8_vdec[] = { + &audio_alaw_no_name, + &video_vp8_no_name, + &video_declined_no_name, + NULL +}; + +/* Sorting by type and deleting a stream */ +static const struct sdp_update_test mrg_by_type_02 = { + .initial = top_ulaw_h264__vp8, + .update_1 = top_alaw__vp8, + .expected = top_alaw__vp8_vdec, +}; + + +static const struct sdp_topology_stream *top_h264_alaw_ulaw[] = { + &video_h264_no_name, + &audio_alaw_no_name, + &audio_ulaw_no_name, + NULL +}; + +static const struct sdp_topology_stream *top__t38[] = { + &image_t38_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_vdec__t38_adec[] = { + &video_declined_no_name, + &image_t38_no_name, + &audio_declined_no_name, + NULL +}; + +/* Sorting by type changing stream types for T.38 */ +static const struct sdp_update_test mrg_by_type_03 = { + .initial = top_h264_alaw_ulaw, + .update_1 = top__t38, + .expected = top_vdec__t38_adec, +}; + + +/* Sorting by type changing stream types back from T.38 */ +static const struct sdp_update_test mrg_by_type_04 = { + .initial = top_vdec__t38_adec, + .update_1 = top_h264_alaw_ulaw, + .expected = top_h264_alaw_ulaw, +}; + + +static const struct sdp_topology_stream *top_h264[] = { + &video_h264_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_vdec__t38[] = { + &video_declined_no_name, + &image_t38_no_name, + NULL +}; + +/* Sorting by type changing stream types for T.38 */ +static const struct sdp_update_test mrg_by_type_05 = { + .initial = top_h264, + .update_1 = top__t38, + .expected = top_vdec__t38, +}; + + +static const struct sdp_topology_stream *top_h264_idec[] = { + &video_h264_no_name, + &image_declined_no_name, + NULL +}; + +/* Sorting by type changing stream types back from T.38 */ +static const struct sdp_update_test mrg_by_type_06 = { + .initial = top_vdec__t38, + .update_1 = top_h264, + .expected = top_h264_idec, +}; + + +static const struct sdp_topology_stream *top_ulaw_adec_h264__vp8[] = { + &audio_ulaw_no_name, + &audio_declined_no_name, + &video_h264_no_name, + &video_vp8_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_h263_alaw_h261_h264_vp8[] = { + &video_h263_no_name, + &audio_alaw_no_name, + &video_h261_no_name, + &video_h264_no_name, + &video_vp8_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_alaw_h264_h263_h261_vp8[] = { + &audio_alaw_no_name, + &video_h264_no_name, + &video_h263_no_name, + &video_h261_no_name, + &video_vp8_no_name, + NULL +}; + +/* Sorting by type with backfill and adding streams */ +static const struct sdp_update_test mrg_by_type_07 = { + .initial = top_ulaw_adec_h264__vp8, + .update_1 = top_h263_alaw_h261_h264_vp8, + .expected = top_alaw_h264_h263_h261_vp8, +}; + + +static const struct sdp_topology_stream *top_ulaw_alaw_h264__vp8_h261[] = { + &audio_ulaw_no_name, + &audio_alaw_no_name, + &video_h264_no_name, + &video_vp8_no_name, + &video_h261_no_name, + NULL +}; + +/* Sorting by type overlimit of 4 and drop */ +static const struct sdp_update_test mrg_by_type_08 = { + .max_streams = 4, + .initial = top_ulaw_alaw_h264__vp8, + .update_1 = top_ulaw_alaw_h264__vp8_h261, + .expected = top_ulaw_alaw_h264__vp8, +}; + + +static const struct sdp_topology_stream *top_ulaw_alaw_h264[] = { + &audio_ulaw_no_name, + &audio_alaw_no_name, + &video_h264_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_alaw_h261__vp8[] = { + &audio_alaw_no_name, + &video_h261_no_name, + &video_vp8_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_alaw_adec_h261__vp8[] = { + &audio_alaw_no_name, + &audio_declined_no_name, + &video_h261_no_name, + &video_vp8_no_name, + NULL +}; + +/* Sorting by type with delete and add of streams */ +static const struct sdp_update_test mrg_by_type_09 = { + .initial = top_ulaw_alaw_h264, + .update_1 = top_alaw_h261__vp8, + .expected = top_alaw_adec_h261__vp8, +}; + + +static const struct sdp_topology_stream *top_ulaw_adec_h264[] = { + &audio_ulaw_no_name, + &audio_declined_no_name, + &video_h264_no_name, + NULL +}; + +/* Sorting by type and adding streams */ +static const struct sdp_update_test mrg_by_type_10 = { + .initial = top_ulaw_adec_h264, + .update_1 = top_alaw_ulaw__vp8_h264, + .expected = top_alaw_ulaw__vp8_h264, +}; + + +static const struct sdp_topology_stream *top_adec_g722_h261[] = { + &audio_declined_no_name, + &audio_g722_no_name, + &video_h261_no_name, + NULL +}; + +/* Sorting by type and deleting old streams */ +static const struct sdp_update_test mrg_by_type_11 = { + .initial = top_ulaw_alaw_h264, + .update_1 = top_adec_g722_h261, + .expected = top_adec_g722_h261, +}; + + +static const struct sdp_topology_stream audio_alaw4dave = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", "dave" +}; + +static const struct sdp_topology_stream audio_g7224dave = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", "dave" +}; + +static const struct sdp_topology_stream audio_ulaw4fred = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", "fred" +}; + +static const struct sdp_topology_stream audio_alaw4fred = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", "fred" +}; + +static const struct sdp_topology_stream audio_ulaw4rose = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", "rose" +}; + +static const struct sdp_topology_stream audio_g7224rose = { + AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", "rose" +}; + + +static const struct sdp_topology_stream video_h2614dave = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", "dave" +}; + +static const struct sdp_topology_stream video_h2634dave = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", "dave" +}; + +static const struct sdp_topology_stream video_h2634fred = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", "fred" +}; + +static const struct sdp_topology_stream video_h2644fred = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", "fred" +}; + +static const struct sdp_topology_stream video_h2644rose = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", "rose" +}; + +static const struct sdp_topology_stream video_h2614rose = { + AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", "rose" +}; + + +static const struct sdp_topology_stream *top_adave_alaw_afred_ulaw_arose_g722_vdave_h261_vfred_h263_vrose_h264[] = { + &audio_alaw4dave, + &audio_alaw_no_name, + &audio_ulaw4fred, + &audio_ulaw_no_name, + &audio_g7224rose, + &audio_g722_no_name, + &video_h2614dave, + &video_h261_no_name, + &video_h2634fred, + &video_h263_no_name, + &video_h2644rose, + &video_h264_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_vfred_vrose_vdave_h263_h264_h261_afred_ulaw_arose_g722_adave_alaw[] = { + &video_h2644fred, + &video_h2614rose, + &video_h2634dave, + &video_h263_no_name, + &video_h264_no_name, + &video_h261_no_name, + &audio_alaw4fred, + &audio_ulaw_no_name, + &audio_ulaw4rose, + &audio_g722_no_name, + &audio_g7224dave, + &audio_alaw_no_name, + NULL +}; + +static const struct sdp_topology_stream *top_adave_ulaw_afred_g722_arose_alaw_vdave_h263_vfred_h264_vrose_h261[] = { + &audio_g7224dave, + &audio_ulaw_no_name, + &audio_alaw4fred, + &audio_g722_no_name, + &audio_ulaw4rose, + &audio_alaw_no_name, + &video_h2634dave, + &video_h263_no_name, + &video_h2644fred, + &video_h264_no_name, + &video_h2614rose, + &video_h261_no_name, + NULL +}; + +/* Sorting by name and type with no new or deleted streams */ +static const struct sdp_update_test mrg_by_name_00 = { + .initial = top_adave_alaw_afred_ulaw_arose_g722_vdave_h261_vfred_h263_vrose_h264, + .update_1 = top_vfred_vrose_vdave_h263_h264_h261_afred_ulaw_arose_g722_adave_alaw, + .expected = top_adave_ulaw_afred_g722_arose_alaw_vdave_h263_vfred_h264_vrose_h261, +}; + + +static const struct sdp_topology_stream *top_adave_g723_h261[] = { + &audio_g7224dave, + &audio_g723_no_name, + &video_h261_no_name, + NULL +}; + +/* Sorting by name and type adding names to streams */ +static const struct sdp_update_test mrg_by_name_01 = { + .initial = top_ulaw_alaw_h264, + .update_1 = top_adave_g723_h261, + .expected = top_adave_g723_h261, +}; + + +/* Sorting by name and type removing names from streams */ +static const struct sdp_update_test mrg_by_name_02 = { + .initial = top_adave_g723_h261, + .update_1 = top_ulaw_alaw_h264, + .expected = top_ulaw_alaw_h264, +}; + + +static const struct sdp_update_test *sdp_update_cases[] = { + /* Merging by type */ + /* 00 */ &mrg_by_type_00, + /* 01 */ &mrg_by_type_01, + /* 02 */ &mrg_by_type_02, + /* 03 */ &mrg_by_type_03, + /* 04 */ &mrg_by_type_04, + /* 05 */ &mrg_by_type_05, + /* 06 */ &mrg_by_type_06, + /* 07 */ &mrg_by_type_07, + /* 08 */ &mrg_by_type_08, + /* 09 */ &mrg_by_type_09, + /* 10 */ &mrg_by_type_10, + /* 11 */ &mrg_by_type_11, + + /* Merging by name and type */ + /* 12 */ &mrg_by_name_00, + /* 13 */ &mrg_by_name_01, + /* 14 */ &mrg_by_name_02, +}; + +AST_TEST_DEFINE(sdp_update_topology) +{ + enum ast_test_result_state res; + unsigned int idx; + int status; + struct ast_sdp_options *options; + struct ast_stream_topology *topology; + struct ast_sdp_state *test_state = NULL; + + static const struct sdp_format sdp_formats[] = { + { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,g723" }, + { AST_MEDIA_TYPE_VIDEO, "h261,h263,h264,vp8" }, + { AST_MEDIA_TYPE_IMAGE, "t38" }, + }; + + switch(cmd) { + case TEST_INIT: + info->name = "sdp_update_topology"; + info->category = "/main/sdp/"; + info->summary = "Merge topology updates from the system"; + info->description = + "1) Create a SDP state with an optional initial topology.\n" + "2) Update the initial topology with one or two new topologies.\n" + "3) Get the SDP offer to merge the updates into the initial topology.\n" + "4) Check that the offered topology matches the expected topology.\n" + "5) Repeat these steps for each test case defined."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + res = AST_TEST_FAIL; + for (idx = 0; idx < ARRAY_LEN(sdp_update_cases); ++idx) { + ast_test_status_update(test, "Starting update case %d\n", idx); + + /* Create a SDP state with an optional initial topology. */ + options = sdp_options_common(); + if (!options) { + ast_test_status_update(test, "Failed to allocate SDP options\n"); + goto end; + } + if (sdp_update_cases[idx]->max_streams) { + ast_sdp_options_set_max_streams(options, sdp_update_cases[idx]->max_streams); + } + if (build_sdp_option_formats(options, ARRAY_LEN(sdp_formats), sdp_formats)) { + ast_test_status_update(test, "Failed to setup SDP options new stream formats\n"); + goto end; + } + if (sdp_update_cases[idx]->initial) { + topology = build_update_topology(sdp_update_cases[idx]->initial); + if (!topology) { + ast_test_status_update(test, "Failed to build initial SDP state topology\n"); + goto end; + } + } else { + topology = NULL; + } + test_state = ast_sdp_state_alloc(topology, options); + ast_stream_topology_free(topology); + if (!test_state) { + ast_test_status_update(test, "Failed to build SDP state\n"); + goto end; + } + + /* Update the initial topology with one or two new topologies. */ + topology = build_update_topology(sdp_update_cases[idx]->update_1); + if (!topology) { + ast_test_status_update(test, "Failed to build first update SDP state topology\n"); + goto end; + } + status = ast_sdp_state_update_local_topology(test_state, topology); + ast_stream_topology_free(topology); + if (status) { + ast_test_status_update(test, "Failed to update first update SDP state topology\n"); + goto end; + } + if (sdp_update_cases[idx]->update_2) { + topology = build_update_topology(sdp_update_cases[idx]->update_2); + if (!topology) { + ast_test_status_update(test, "Failed to build second update SDP state topology\n"); + goto end; + } + status = ast_sdp_state_update_local_topology(test_state, topology); + ast_stream_topology_free(topology); + if (status) { + ast_test_status_update(test, "Failed to update second update SDP state topology\n"); + goto end; + } + } + + /* Get the SDP offer to merge the updates into the initial topology. */ + if (!ast_sdp_state_get_local_sdp(test_state)) { + ast_test_status_update(test, "Failed to create offer SDP\n"); + goto end; + } + + /* Check that the offered topology matches the expected topology. */ + topology = build_update_topology(sdp_update_cases[idx]->expected); + if (!topology) { + ast_test_status_update(test, "Failed to build expected topology\n"); + goto end; + } + status = cmp_update_topology(test, topology, + ast_sdp_state_get_local_topology(test_state)); + ast_stream_topology_free(topology); + if (status) { + ast_test_status_update(test, "Failed to match expected topology\n"); + goto end; + } + + /* Repeat for each test case defined. */ + ast_sdp_state_free(test_state); + test_state = NULL; + } + res = AST_TEST_PASS; + +end: + ast_sdp_state_free(test_state); + return res; +} + static int unload_module(void) { AST_TEST_UNREGISTER(invalid_rtpmap); @@ -1186,10 +2014,13 @@ static int unload_module(void) AST_TEST_UNREGISTER(find_attr); AST_TEST_UNREGISTER(topology_to_sdp); AST_TEST_UNREGISTER(sdp_to_topology); - AST_TEST_UNREGISTER(sdp_merge_symmetric); - AST_TEST_UNREGISTER(sdp_merge_crisscross); - AST_TEST_UNREGISTER(sdp_merge_asymmetric); + AST_TEST_UNREGISTER(sdp_negotiation_initial); + AST_TEST_UNREGISTER(sdp_negotiation_type_change); + AST_TEST_UNREGISTER(sdp_negotiation_decline_incompatible); + AST_TEST_UNREGISTER(sdp_negotiation_decline_max_streams); + AST_TEST_UNREGISTER(sdp_negotiation_not_acceptable); AST_TEST_UNREGISTER(sdp_ssrc_attributes); + AST_TEST_UNREGISTER(sdp_update_topology); return 0; } @@ -1201,10 +2032,13 @@ static int load_module(void) AST_TEST_REGISTER(find_attr); AST_TEST_REGISTER(topology_to_sdp); AST_TEST_REGISTER(sdp_to_topology); - AST_TEST_REGISTER(sdp_merge_symmetric); - AST_TEST_REGISTER(sdp_merge_crisscross); - AST_TEST_REGISTER(sdp_merge_asymmetric); + AST_TEST_REGISTER(sdp_negotiation_initial); + AST_TEST_REGISTER(sdp_negotiation_type_change); + AST_TEST_REGISTER(sdp_negotiation_decline_incompatible); + AST_TEST_REGISTER(sdp_negotiation_decline_max_streams); + AST_TEST_REGISTER(sdp_negotiation_not_acceptable); AST_TEST_REGISTER(sdp_ssrc_attributes); + AST_TEST_REGISTER(sdp_update_topology); return AST_MODULE_LOAD_SUCCESS; } |