<p>Joshua Colp <strong>merged</strong> this change.</p><p><a href="https://gerrit.asterisk.org/5819">View Change</a></p><div style="white-space:pre-wrap">Approvals:
Joshua Colp: Looks good to me, but someone else must approve; Approved for Submit
George Joseph: Looks good to me, approved
</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">SDP: Rework SDP offer/answer model and update capabilities merges.<br><br>The SDP offer/answer model requires an answer to an offer before a new SDP<br>can be processed. This allows our local SDP creation to be deferred until<br>we know that we need to create an offer or an answer SDP. Once the local<br>SDP is created it won't change until the SDP negotiation is restarted.<br><br>An offer SDP in an initial SIP INVITE can receive more than one answer<br>SDP. In this case, we need to merge each answer SDP with our original<br>offer capabilities to get the currently negotiated capabilities. To<br>satisfy this requirement means that we cannot update our proposed<br>capabilities until the negotiations are restarted.<br><br>Local topology updates from ast_sdp_state_update_local_topology() are<br>merged together until the next offer SDP is created. These accumulated<br>updates are then merged with the current negotiated capabilities to create<br>the new proposed capabilities that the offer SDP is built.<br><br>Local topology updates are merged in several passes to attempt to be smart<br>about how streams from the system are matched with the previously<br>negotiated stream slots. To allow for T.38 support when merging, type<br>matching considers audio and image types to be equivalent. First streams<br>are matched by stream name and type. Then streams are matched by stream<br>type only. Any remaining unmatched existing streams are declined. Any<br>new active streams are either backfilled into pre-merge declined slots or<br>appended onto the end of the merged topology. Any excess new streams<br>above the maximum supported number of streams are simply discarded.<br><br>Remote topology negotiation merges depend if the topology is an offer or<br>answer. An offer remote topology negotiation dictates the stream slot<br>ordering and new streams can be added. A remote offer can do anything to<br>the previously negotiated streams except reduce the number of stream<br>slots. An answer remote topology negotiation is limited to what our offer<br>requested. The answer can only decline streams, pick codecs from the<br>offered list, or indicate the remote's stream hold state.<br><br>I had originally kept the RTP instance if the remote offer SDP changed a<br>stream type between audio and video since they both use RTP. However, I<br>later removed this support in favor of simply creating a new RTP instance<br>since the stream's purpose has to be changing anyway. Any RTP packets<br>from the old stream type might cause mischief for the bridged peer.<br><br>* Added ast_sdp_state_restart_negotiations() to restart the SDP<br>offer/answer negotiations. We will thus know to create a new local SDP<br>when it is time to create an offer or answer.<br><br>* Removed ast_sdp_state_reset(). Save the current topology before<br>starting T.38. To recover from T.38 simply update the local topology to<br>the saved topology and restart the SDP negotiations to get the offer SDP<br>renegotiating the previous configuration.<br><br>* Allow initial topology for ast_sdp_state_alloc() to be NULL so an<br>initial remote offer SDP can dictate the streams we start with. We can<br>always update the local topology later if it turns out we need to offer<br>SDP first because the remote chose to defer sending us a SDP.<br><br>* Made the ast_sdp_state_alloc() initial topology limit to max_streams,<br>limit to configured codecs, handle declined streams, and discard<br>unsupported types.<br><br>* Convert struct ast_sdp to ao2 object. Needed to easily save off a<br>remote SDP to refer to later for various reasons such as generating<br>declined m= lines in the local SDP.<br><br>* Improve converting remote SDP streams to a topology including stream<br>state. A stream state of AST_STREAM_STATE_REMOVED indicates the stream is<br>declined/dead.<br><br>* Improve merging streams to take into account the stream state.<br><br>* Added query for remote hold state.<br><br>* Added maximum streams allowed SDP config option.<br><br>* Added ability to create new streams as needed. New streams are created<br>with configured default audio, video, or image codecs depending on stream<br>type.<br><br>* Added global locally_held state along with a per stream local hold<br>state. Historically, Asterisk only has a global locally held state<br>because when the we put the remote on hold we do it for all active<br>streams.<br><br>* Added queries for a rejected offer and current SDP negotiation role.<br>The rejected query allows the using module to know how to respond to a<br>failed remote SDP set. Should the using module respond with a 488 Not<br>Acceptable Here or 500 Internal Error to the offer SDP?<br><br>* Moved sdp_state_capabilities.connection_address to ast_sdp_state. There<br>seems no reason to keep it in the sdp_state_capabilities struct since it<br>was only used by the ast_sdp_state.proposed_capabilities instance.<br><br>* Callbacks are now available to allow the using module some customization<br>of negotiated streams and to complete setting up streams for use. See the<br>typedef doxygen for each callback for what is allowable and when they are<br>called.<br> * Added topology answerer modify callback.<br> * Added topology pre and post apply callbacks.<br> * Added topology offerer modify callback.<br> * Added topology offerer configure callback.<br><br>* Had to rework the unit tests because I changed how SDP topologies are<br>merged. Replaced several unit tests with new negotiation tests.<br><br>Change-Id: If07fe6d79fbdce33968a9401d41d908385043a06<br>---<br>M include/asterisk/sdp.h<br>M include/asterisk/sdp_options.h<br>M include/asterisk/sdp_state.h<br>M include/asterisk/stream.h<br>M main/sdp.c<br>M main/sdp_options.c<br>M main/sdp_private.h<br>M main/sdp_state.c<br>M main/stream.c<br>M res/res_sdp_translator_pjmedia.c<br>M tests/test_sdp.c<br>11 files changed, 3,400 insertions(+), 612 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">diff --git a/include/asterisk/sdp.h b/include/asterisk/sdp.h<br>index 224a0e5..7684695 100644<br>--- a/include/asterisk/sdp.h<br>+++ b/include/asterisk/sdp.h<br>@@ -254,16 +254,6 @@<br> void ast_sdp_t_free(struct ast_sdp_t_line *t_line);<br> <br> /*!<br>- * \brief Free an SDP<br>- * Frees the sdp and all resources it contains<br>- *<br>- * \param sdp The sdp to free<br>- *<br>- * \since 15<br>- */<br>-void ast_sdp_free(struct ast_sdp *sdp);<br>-<br>-/*!<br> * \brief Allocate an SDP Attribute<br> *<br> * \param name Attribute Name<br>@@ -544,7 +534,7 @@<br> int rtp_code, int asterisk_format, const struct ast_format *format, int code);<br> <br> /*!<br>- * \brief Create an SDP<br>+ * \brief Create an SDP ao2 object<br> *<br> * \param o_line Origin<br> * \param c_line Connection<br>diff --git a/include/asterisk/sdp_options.h b/include/asterisk/sdp_options.h<br>index b8c1bbd..e45ae8c 100644<br>--- a/include/asterisk/sdp_options.h<br>+++ b/include/asterisk/sdp_options.h<br>@@ -20,6 +20,7 @@<br> #define _ASTERISK_SDP_OPTIONS_H<br> <br> #include "asterisk/udptl.h"<br>+#include "asterisk/format_cap.h"<br> <br> struct ast_sdp_options;<br> <br>@@ -78,6 +79,149 @@<br> /*! DTLS encryption */<br> AST_SDP_ENCRYPTION_DTLS,<br> };<br>+<br>+/*!<br>+ * \brief Callback when processing an offer SDP for our answer SDP.<br>+ * \since 15.0.0<br>+ *<br>+ * \details<br>+ * This callback is called after merging our last negotiated topology<br>+ * with the remote's offer topology and before we have sent our answer<br>+ * SDP. At this point you can alter new_topology streams. You can<br>+ * decline, remove formats, or rename streams. Changing anything else<br>+ * on the streams is likely to not end well.<br>+ *<br>+ * * To decline a stream simply set the stream state to<br>+ * AST_STREAM_STATE_REMOVED. You could implement a maximum number<br>+ * of active streams of a given type policy.<br>+ *<br>+ * * To remove formats use the format API to remove any formats from a<br>+ * stream. The streams have the current joint negotiated formats.<br>+ * Most likely you would want to remove all but the first format.<br>+ *<br>+ * * To rename a stream you need to clone the stream and give it a<br>+ * new name and then set it in new_topology using<br>+ * ast_stream_topology_set_stream().<br>+ *<br>+ * \note Removing all formats is an error. You should decline the<br>+ * stream instead.<br>+ *<br>+ * \param context User supplied context data pointer for the SDP<br>+ * state.<br>+ * \param old_topology Active negotiated topology. NULL if this is<br>+ * the first SDP negotiation. The old topology is available so you<br>+ * can tell if any streams are new or changing type.<br>+ * \param new_topology New negotiated topology that we intend to<br>+ * generate the answer SDP.<br>+ *<br>+ * \return Nothing<br>+ */<br>+typedef void (*ast_sdp_answerer_modify_cb)(void *context,<br>+ const struct ast_stream_topology *old_topology,<br>+ struct ast_stream_topology *new_topology);<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Callback when generating a topology for our SDP offer.<br>+ * \since 15.0.0<br>+ *<br>+ * \details<br>+ * This callback is called after merging any topology updates from the<br>+ * system by ast_sdp_state_update_local_topology() and before we have<br>+ * sent our offer SDP. At this point you can alter new_topology<br>+ * streams. You can decline, add/remove/update formats, or rename<br>+ * streams. Changing anything else on the streams is likely to not<br>+ * end well.<br>+ *<br>+ * * To decline a stream simply set the stream state to<br>+ * AST_STREAM_STATE_REMOVED. You could implement a maximum number<br>+ * of active streams of a given type policy.<br>+ *<br>+ * * To update formats use the format API to change formats of the<br>+ * streams. The streams have the current proposed formats. You<br>+ * could do whatever you want for formats but you should stay within<br>+ * the configured formats for the stream type's endpoint. However,<br>+ * you should use ast_sdp_state_update_local_topology() instead of<br>+ * this backdoor method.<br>+ *<br>+ * * To rename a stream you need to clone the stream and give it a<br>+ * new name and then set it in new_topology using<br>+ * ast_stream_topology_set_stream().<br>+ *<br>+ * \note Removing all formats is an error. You should decline the<br>+ * stream instead.<br>+ *<br>+ * \note Declined new streams that are in slots higher than present in<br>+ * old_topology are removed so the SDP can be smaller. The remote has<br>+ * never seen those slots so we shouldn't bother keeping them.<br>+ *<br>+ * \param context User supplied context data pointer for the SDP<br>+ * state.<br>+ * \param old_topology Active negotiated topology. NULL if this is<br>+ * the first SDP negotiation. The old topology is available so you<br>+ * can tell if any streams are new or changing type.<br>+ * \param new_topology Merged topology that we intend to generate the<br>+ * offer SDP.<br>+ *<br>+ * \return Nothing<br>+ */<br>+typedef void (*ast_sdp_offerer_modify_cb)(void *context,<br>+ const struct ast_stream_topology *old_topology,<br>+ struct ast_stream_topology *new_topology);<br>+<br>+/*!<br>+ * \brief Callback when generating an offer SDP to configure extra stream data.<br>+ * \since 15.0.0<br>+ *<br>+ * \details<br>+ * This callback is called after any ast_sdp_offerer_modify_cb<br>+ * callback and before we have sent our offer SDP. The callback can<br>+ * call several SDP API calls to configure the proposed capabilities<br>+ * of streams before we create the SDP offer. For example, the<br>+ * callback could configure a stream specific connection address, T.38<br>+ * parameters, RTP instance, or UDPTL instance parameters.<br>+ *<br>+ * \param context User supplied context data pointer for the SDP<br>+ * state.<br>+ * \param topology Topology ready to configure extra stream options.<br>+ *<br>+ * \return Nothing<br>+ */<br>+typedef void (*ast_sdp_offerer_config_cb)(void *context, const struct ast_stream_topology *topology);<br>+<br>+/*!<br>+ * \brief Callback before applying a topology.<br>+ * \since 15.0.0<br>+ *<br>+ * \details<br>+ * This callback is called before the topology is applied so the<br>+ * using module can do what is necessary before the topology becomes<br>+ * active.<br>+ *<br>+ * \param context User supplied context data pointer for the SDP<br>+ * state.<br>+ * \param topology Topology ready to be applied.<br>+ *<br>+ * \return Nothing<br>+ */<br>+typedef void (*ast_sdp_preapply_cb)(void *context, const struct ast_stream_topology *topology);<br>+<br>+/*!<br>+ * \brief Callback after applying a topology.<br>+ * \since 15.0.0<br>+ *<br>+ * \details<br>+ * This callback is called after the topology is applied so the<br>+ * using module can do what is necessary after the topology becomes<br>+ * active.<br>+ *<br>+ * \param context User supplied context data pointer for the SDP<br>+ * state.<br>+ * \param topology Topology already applied.<br>+ *<br>+ * \return Nothing<br>+ */<br>+typedef void (*ast_sdp_postapply_cb)(void *context, const struct ast_stream_topology *topology);<br> <br> /*!<br> * \since 15.0.0<br>@@ -203,6 +347,24 @@<br> * \returns rtp_engine<br> */<br> const char *ast_sdp_options_get_rtp_engine(const struct ast_sdp_options *options);<br>+<br>+void ast_sdp_options_set_state_context(struct ast_sdp_options *options, void *state_context);<br>+void *ast_sdp_options_get_state_context(const struct ast_sdp_options *options);<br>+<br>+void ast_sdp_options_set_answerer_modify_cb(struct ast_sdp_options *options, ast_sdp_answerer_modify_cb answerer_modify_cb);<br>+ast_sdp_answerer_modify_cb ast_sdp_options_get_answerer_modify_cb(const struct ast_sdp_options *options);<br>+<br>+void ast_sdp_options_set_offerer_modify_cb(struct ast_sdp_options *options, ast_sdp_offerer_modify_cb offerer_modify_cb);<br>+ast_sdp_offerer_modify_cb ast_sdp_options_get_offerer_modify_cb(const struct ast_sdp_options *options);<br>+<br>+void ast_sdp_options_set_offerer_config_cb(struct ast_sdp_options *options, ast_sdp_offerer_config_cb offerer_config_cb);<br>+ast_sdp_offerer_config_cb ast_sdp_options_get_offerer_config_cb(const struct ast_sdp_options *options);<br>+<br>+void ast_sdp_options_set_preapply_cb(struct ast_sdp_options *options, ast_sdp_preapply_cb preapply_cb);<br>+ast_sdp_preapply_cb ast_sdp_options_get_preapply_cb(const struct ast_sdp_options *options);<br>+<br>+void ast_sdp_options_set_postapply_cb(struct ast_sdp_options *options, ast_sdp_postapply_cb postapply_cb);<br>+ast_sdp_postapply_cb ast_sdp_options_get_postapply_cb(const struct ast_sdp_options *options);<br> <br> /*!<br> * \since 15.0.0<br>@@ -505,6 +667,26 @@<br> <br> /*!<br> * \since 15.0.0<br>+ * \brief Set SDP Options max_streams<br>+ *<br>+ * \param options SDP Options<br>+ * \param max_streams<br>+ */<br>+void ast_sdp_options_set_max_streams(struct ast_sdp_options *options,<br>+ unsigned int max_streams);<br>+<br>+/*!<br>+ * \since 15.0.0<br>+ * \brief Get SDP Options max_streams<br>+ *<br>+ * \param options SDP Options<br>+ *<br>+ * \returns max_streams<br>+ */<br>+unsigned int ast_sdp_options_get_max_streams(const struct ast_sdp_options *options);<br>+<br>+/*!<br>+ * \since 15.0.0<br> * \brief Enable setting SSRC level attributes on SDPs<br> *<br> * \param options SDP Options<br>@@ -547,4 +729,46 @@<br> struct ast_sched_context *ast_sdp_options_get_sched_type(const struct ast_sdp_options *options,<br> enum ast_media_type type);<br> <br>+/*!<br>+ * \brief Set all allowed stream types to create new streams.<br>+ * \since 15.0.0<br>+ *<br>+ * \param options SDP Options<br>+ * \param cap Format capabilities to set all allowed stream types at once.<br>+ * Could be NULL to disable creating any new streams.<br>+ *<br>+ * \return Nothing<br>+ */<br>+void ast_sdp_options_set_format_caps(struct ast_sdp_options *options,<br>+ struct ast_format_cap *cap);<br>+<br>+/*!<br>+ * \brief Set the SDP options format cap used to create new streams of the type.<br>+ * \since 15.0.0<br>+ *<br>+ * \param options SDP Options<br>+ * \param type Media type the format cap represents.<br>+ * \param cap Format capabilities to use for the specified media type.<br>+ * Could be NULL to disable creating new streams of type.<br>+ *<br>+ * \return Nothing<br>+ */<br>+void ast_sdp_options_set_format_cap_type(struct ast_sdp_options *options,<br>+ enum ast_media_type type, struct ast_format_cap *cap);<br>+<br>+/*!<br>+ * \brief Get the SDP options format cap used to create new streams of the type.<br>+ * \since 15.0.0<br>+ *<br>+ * \param options SDP Options<br>+ * \param type Media type the format cap represents.<br>+ *<br>+ * \retval NULL if stream not allowed to be created.<br>+ * \retval cap to use in negotiating the new stream.<br>+ *<br>+ * \note The returned cap does not have its own ao2 ref.<br>+ */<br>+struct ast_format_cap *ast_sdp_options_get_format_cap_type(const struct ast_sdp_options *options,<br>+ enum ast_media_type type);<br>+<br> #endif /* _ASTERISK_SDP_OPTIONS_H */<br>diff --git a/include/asterisk/sdp_state.h b/include/asterisk/sdp_state.h<br>index b8209e1..ec9d502 100644<br>--- a/include/asterisk/sdp_state.h<br>+++ b/include/asterisk/sdp_state.h<br>@@ -30,12 +30,22 @@<br> /*!<br> * \brief Allocate a new SDP state<br> *<br>+ * \details<br> * SDP state keeps tabs on everything SDP-related for a media session.<br> * Most SDP operations will require the state to be provided.<br> * Ownership of the SDP options is taken on by the SDP state.<br> * A good strategy is to call this during session creation.<br>+ *<br>+ * \param topology Initial stream topology to offer.<br>+ * NULL if we are going to be the answerer. We can always<br>+ * update the local topology later if it turns out we need<br>+ * to be the offerer.<br>+ * \param options SDP options for the duration of the session.<br>+ *<br>+ * \retval SDP state struct<br>+ * \retval NULL on failure<br> */<br>-struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams,<br>+struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *topology,<br> struct ast_sdp_options *options);<br> <br> /*!<br>@@ -86,6 +96,8 @@<br> *<br> * If this is called prior to receiving a remote SDP, then this will just mirror<br> * the local configured endpoint capabilities.<br>+ *<br>+ * \note Cannot return NULL. It is a BUG if it does.<br> */<br> const struct ast_stream_topology *ast_sdp_state_get_joint_topology(<br> const struct ast_sdp_state *sdp_state);<br>@@ -93,6 +105,7 @@<br> /*!<br> * \brief Get the local topology<br> *<br>+ * \note Cannot return NULL. It is a BUG if it does.<br> */<br> const struct ast_stream_topology *ast_sdp_state_get_local_topology(<br> const struct ast_sdp_state *sdp_state);<br>@@ -114,9 +127,10 @@<br> * \retval NULL Failure<br> *<br> * \note<br>- * This function will allocate a new SDP with RTP instances if it has not already<br>- * been allocated.<br>- *<br>+ * This function will return the last local SDP created if one were<br>+ * previously requested for the current negotiation. Otherwise it<br>+ * creates our SDP offer/answer depending on what role we are playing<br>+ * in the current negotiation.<br> */<br> const struct ast_sdp *ast_sdp_state_get_local_sdp(struct ast_sdp_state *sdp_state);<br> <br>@@ -152,6 +166,7 @@<br> *<br> * \retval 0 Success<br> * \retval non-0 Failure<br>+ * Use ast_sdp_state_is_offer_rejected() to see if the SDP offer was rejected.<br> *<br> * \since 15<br> */<br>@@ -165,39 +180,72 @@<br> *<br> * \retval 0 Success<br> * \retval non-0 Failure<br>+ * Use ast_sdp_state_is_offer_rejected() to see if the SDP offer was rejected.<br> *<br> * \since 15<br> */<br> int ast_sdp_state_set_remote_sdp_from_impl(struct ast_sdp_state *sdp_state, const void *remote);<br> <br> /*!<br>- * \brief Reset the SDP state and stream capabilities as if the SDP state had just been allocated.<br>+ * \brief Was the set remote offer rejected.<br>+ * \since 15.0.0<br> *<br> * \param sdp_state<br>- * \param remote The implementation's representation of an SDP.<br>+ *<br>+ * \retval 0 if not rejected.<br>+ * \retval non-zero if rejected.<br>+ */<br>+int ast_sdp_state_is_offer_rejected(struct ast_sdp_state *sdp_state);<br>+<br>+/*!<br>+ * \brief Are we the SDP offerer.<br>+ * \since 15.0.0<br>+ *<br>+ * \param sdp_state<br>+ *<br>+ * \retval 0 if we are not the offerer.<br>+ * \retval non-zero we are the offerer.<br>+ */<br>+int ast_sdp_state_is_offerer(struct ast_sdp_state *sdp_state);<br>+<br>+/*!<br>+ * \brief Are we the SDP answerer.<br>+ * \since 15.0.0<br>+ *<br>+ * \param sdp_state<br>+ *<br>+ * \retval 0 if we are not the answerer.<br>+ * \retval non-zero we are the answerer.<br>+ */<br>+int ast_sdp_state_is_answerer(struct ast_sdp_state *sdp_state);<br>+<br>+/*!<br>+ * \brief Restart the SDP offer/answer negotiations.<br>+ *<br>+ * \param sdp_state<br> *<br> * \retval 0 Success<br>- *<br>- * \note<br>- * This is most useful for when a channel driver is sending a session refresh message<br>- * and needs to re-advertise its initial capabilities instead of the previously-negotiated<br>- * joint capabilities.<br>- *<br>- * \since 15<br>+ * \retval non-0 Failure<br> */<br>-int ast_sdp_state_reset(struct ast_sdp_state *sdp_state);<br>+int ast_sdp_state_restart_negotiations(struct ast_sdp_state *sdp_state);<br> <br> /*!<br> * \brief Update the local stream topology on the SDP state.<br> *<br>+ * \details<br>+ * Basically we are saving off any topology updates until we create the<br>+ * next SDP offer. Repeated updates merge with the previous updated<br>+ * topology.<br>+ *<br> * \param sdp_state<br>- * \param streams The new stream topology.<br>+ * \param topology The new stream topology.<br> *<br> * \retval 0 Success<br>+ * \retval non-0 Failure<br> *<br> * \since 15<br> */<br>-int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *streams);<br>+int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *topology);<br> <br> /*!<br> * \brief Set the local address (IP address) to use for connection addresses<br>@@ -231,7 +279,26 @@<br> <br> /*!<br> * \since 15.0.0<br>- * \brief Set a stream to be held or unheld<br>+ * \brief Set the global locally held state.<br>+ *<br>+ * \param sdp_state<br>+ * \param locally_held<br>+ */<br>+void ast_sdp_state_set_global_locally_held(struct ast_sdp_state *sdp_state, unsigned int locally_held);<br>+<br>+/*!<br>+ * \since 15.0.0<br>+ * \brief Get the global locally held state.<br>+ *<br>+ * \param sdp_state<br>+ *<br>+ * \returns locally_held<br>+ */<br>+unsigned int ast_sdp_state_get_global_locally_held(const struct ast_sdp_state *sdp_state);<br>+<br>+/*!<br>+ * \since 15.0.0<br>+ * \brief Set a stream to be held or unheld locally<br> *<br> * \param sdp_state<br> * \param stream_index The stream to set the held value for<br>@@ -239,6 +306,30 @@<br> */<br> void ast_sdp_state_set_locally_held(struct ast_sdp_state *sdp_state,<br> int stream_index, unsigned int locally_held);<br>+<br>+/*!<br>+ * \since 15.0.0<br>+ * \brief Get whether a stream is locally held or not<br>+ *<br>+ * \param sdp_state<br>+ * \param stream_index The stream to get the held state for<br>+ *<br>+ * \returns locally_held<br>+ */<br>+unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state,<br>+ int stream_index);<br>+<br>+/*!<br>+ * \since 15.0.0<br>+ * \brief Get whether a stream is remotely held or not<br>+ *<br>+ * \param sdp_state<br>+ * \param stream_index The stream to get the held state for<br>+ *<br>+ * \returns remotely_held<br>+ */<br>+unsigned int ast_sdp_state_get_remotely_held(const struct ast_sdp_state *sdp_state,<br>+ int stream_index);<br> <br> /*!<br> * \since 15.0.0<br>@@ -250,17 +341,5 @@<br> */<br> void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state,<br> int stream_index, struct ast_control_t38_parameters *params);<br>-<br>-/*!<br>- * \since 15.0.0<br>- * \brief Get whether a stream is held or not<br>- *<br>- * \param sdp_state<br>- * \param stream_index The stream to get the held state for<br>- *<br>- * \returns locally_held<br>- */<br>-unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state,<br>- int stream_index);<br> <br> #endif /* _ASTERISK_SDP_STATE_H */<br>diff --git a/include/asterisk/stream.h b/include/asterisk/stream.h<br>index 00169a3..21af53f 100644<br>--- a/include/asterisk/stream.h<br>+++ b/include/asterisk/stream.h<br>@@ -228,6 +228,17 @@<br> const char *ast_stream_state2str(enum ast_stream_state state);<br> <br> /*!<br>+ * \brief Convert a string to a stream state<br>+ *<br>+ * \param str The string to convert<br>+ *<br>+ * \return The stream state<br>+ *<br>+ * \since 15.0.0<br>+ */<br>+enum ast_stream_state ast_stream_str2state(const char *str);<br>+<br>+/*!<br> * \brief Get the opaque stream data<br> *<br> * \param stream The media stream<br>diff --git a/main/sdp.c b/main/sdp.c<br>index bfb83e8..fd10ba8 100644<br>--- a/main/sdp.c<br>+++ b/main/sdp.c<br>@@ -109,11 +109,9 @@<br> ast_free(t_line);<br> }<br> <br>-void ast_sdp_free(struct ast_sdp *sdp)<br>+static void ast_sdp_dtor(void *vdoomed)<br> {<br>- if (!sdp) {<br>- return;<br>- }<br>+ struct ast_sdp *sdp = vdoomed;<br> <br> ast_sdp_o_free(sdp->o_line);<br> ast_sdp_s_free(sdp->s_line);<br>@@ -121,7 +119,6 @@<br> ast_sdp_t_free(sdp->t_line);<br> ast_sdp_a_lines_free(sdp->a_lines);<br> ast_sdp_m_lines_free(sdp->m_lines);<br>- ast_free(sdp);<br> }<br> <br> #define COPY_STR_AND_ADVANCE(p, dest, source) \<br>@@ -314,28 +311,28 @@<br> {<br> struct ast_sdp *new_sdp;<br> <br>- new_sdp = ast_calloc(1, sizeof *new_sdp);<br>+ new_sdp = ao2_alloc_options(sizeof(*new_sdp), ast_sdp_dtor, AO2_ALLOC_OPT_LOCK_NOLOCK);<br> if (!new_sdp) {<br> return NULL;<br> }<br> <br> new_sdp->a_lines = ast_calloc(1, sizeof(*new_sdp->a_lines));<br> if (!new_sdp->a_lines) {<br>- ast_sdp_free(new_sdp);<br>+ ao2_ref(new_sdp, -1);<br> return NULL;<br> }<br> if (AST_VECTOR_INIT(new_sdp->a_lines, 20)) {<br>- ast_sdp_free(new_sdp);<br>+ ao2_ref(new_sdp, -1);<br> return NULL;<br> }<br> <br> new_sdp->m_lines = ast_calloc(1, sizeof(*new_sdp->m_lines));<br> if (!new_sdp->m_lines) {<br>- ast_sdp_free(new_sdp);<br>+ ao2_ref(new_sdp, -1);<br> return NULL;<br> }<br> if (AST_VECTOR_INIT(new_sdp->m_lines, 20)) {<br>- ast_sdp_free(new_sdp);<br>+ ao2_ref(new_sdp, -1);<br> return NULL;<br> }<br> <br>@@ -741,6 +738,62 @@<br> }<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Determine the RTP stream direction in the given a lines.<br>+ * \since 15.0.0<br>+ *<br>+ * \param a_lines Attribute lines to search.<br>+ *<br>+ * \retval Stream direction on success.<br>+ * \retval AST_STREAM_STATE_REMOVED on failure.<br>+ *<br>+ * \return Nothing<br>+ */<br>+static enum ast_stream_state get_a_line_direction(const struct ast_sdp_a_lines *a_lines)<br>+{<br>+ if (a_lines) {<br>+ enum ast_stream_state direction;<br>+ int idx;<br>+ const struct ast_sdp_a_line *a_line;<br>+<br>+ for (idx = 0; idx < AST_VECTOR_SIZE(a_lines); ++idx) {<br>+ a_line = AST_VECTOR_GET(a_lines, idx);<br>+ direction = ast_stream_str2state(a_line->name);<br>+ if (direction != AST_STREAM_STATE_REMOVED) {<br>+ return direction;<br>+ }<br>+ }<br>+ }<br>+<br>+ return AST_STREAM_STATE_REMOVED;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Determine the RTP stream direction.<br>+ * \since 15.0.0<br>+ *<br>+ * \param a_lines The SDP media global attributes<br>+ * \param m_line The SDP media section to convert<br>+ *<br>+ * \return Stream direction<br>+ */<br>+static enum ast_stream_state get_stream_direction(const struct ast_sdp_a_lines *a_lines, const struct ast_sdp_m_line *m_line)<br>+{<br>+ enum ast_stream_state direction;<br>+<br>+ direction = get_a_line_direction(m_line->a_lines);<br>+ if (direction != AST_STREAM_STATE_REMOVED) {<br>+ return direction;<br>+ }<br>+ direction = get_a_line_direction(a_lines);<br>+ if (direction != AST_STREAM_STATE_REMOVED) {<br>+ return direction;<br>+ }<br>+ return AST_STREAM_STATE_SENDRECV;<br>+}<br>+<br> /*<br> * Needed so we don't have an external function referenced as data.<br> * The dynamic linker doesn't handle that very well.<br>@@ -758,13 +811,14 @@<br> * Given an m-line from an SDP, convert it into an ast_stream structure.<br> * This takes formats, as well as clock-rate and fmtp attributes into account.<br> *<br>+ * \param a_lines The SDP media global attributes<br> * \param m_line The SDP media section to convert<br> * \param g726_non_standard Non-zero if G.726 is non-standard<br> *<br> * \retval NULL An error occurred<br> * \retval non-NULL The converted stream<br> */<br>-static struct ast_stream *get_stream_from_m(const struct ast_sdp_m_line *m_line, int g726_non_standard)<br>+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)<br> {<br> int i;<br> int non_ast_fmts;<br>@@ -793,6 +847,14 @@<br> ao2_ref(caps, -1);<br> return NULL;<br> }<br>+ ast_stream_set_data(stream, AST_STREAM_DATA_RTP_CODECS, codecs,<br>+ (ast_stream_data_free_fn) rtp_codecs_free);<br>+<br>+ if (!m_line->port) {<br>+ /* Stream is declined. There may not be any attributes. */<br>+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);<br>+ break;<br>+ }<br> <br> options = g726_non_standard ? AST_RTP_OPT_G726_NONSTANDARD : 0;<br> for (i = 0; i < ast_sdp_m_get_payload_count(m_line); ++i) {<br>@@ -819,10 +881,16 @@<br> }<br> <br> ast_rtp_codecs_payload_formats(codecs, caps, &non_ast_fmts);<br>- ast_stream_set_data(stream, AST_STREAM_DATA_RTP_CODECS, codecs,<br>- (ast_stream_data_free_fn) rtp_codecs_free);<br>+<br>+ ast_stream_set_state(stream, get_stream_direction(a_lines, m_line));<br> break;<br> case AST_MEDIA_TYPE_IMAGE:<br>+ if (!m_line->port) {<br>+ /* Stream is declined. There may not be any attributes. */<br>+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);<br>+ break;<br>+ }<br>+<br> for (i = 0; i < ast_sdp_m_get_payload_count(m_line); ++i) {<br> struct ast_sdp_payload *payload;<br> <br>@@ -830,12 +898,15 @@<br> payload = ast_sdp_m_get_payload(m_line, i);<br> if (!strcasecmp(payload->fmt, "t38")) {<br> ast_format_cap_append(caps, ast_format_t38, 0);<br>+ ast_stream_set_state(stream, AST_STREAM_STATE_SENDRECV);<br> }<br> }<br> break;<br> case AST_MEDIA_TYPE_UNKNOWN:<br> case AST_MEDIA_TYPE_TEXT:<br> case AST_MEDIA_TYPE_END:<br>+ /* Consider these unsupported streams as declined */<br>+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);<br> break;<br> }<br> <br>@@ -858,7 +929,7 @@<br> for (i = 0; i < ast_sdp_get_m_count(sdp); ++i) {<br> struct ast_stream *stream;<br> <br>- stream = get_stream_from_m(ast_sdp_get_m(sdp, i), g726_non_standard);<br>+ stream = get_stream_from_m(sdp->a_lines, ast_sdp_get_m(sdp, i), g726_non_standard);<br> if (!stream) {<br> /*<br> * The topology cannot match the SDP because<br>diff --git a/main/sdp_options.c b/main/sdp_options.c<br>index a938583..79efbaf 100644<br>--- a/main/sdp_options.c<br>+++ b/main/sdp_options.c<br>@@ -27,6 +27,7 @@<br> #define DEFAULT_ICE AST_SDP_ICE_DISABLED<br> #define DEFAULT_IMPL AST_SDP_IMPL_STRING<br> #define DEFAULT_ENCRYPTION AST_SDP_ENCRYPTION_DISABLED<br>+#define DEFAULT_MAX_STREAMS 16 /* Set to match our PJPROJECT PJMEDIA_MAX_SDP_MEDIA. */<br> <br> #define DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(field, assert_on_null) \<br> void ast_sdp_options_set_##field(struct ast_sdp_options *options, const char *value) \<br>@@ -60,6 +61,12 @@<br> DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(sdpsession, 0);<br> DEFINE_STRINGFIELD_GETTERS_SETTERS_FOR(rtp_engine, 0);<br> <br>+DEFINE_GETTERS_SETTERS_FOR(void *, state_context);<br>+DEFINE_GETTERS_SETTERS_FOR(ast_sdp_answerer_modify_cb, answerer_modify_cb);<br>+DEFINE_GETTERS_SETTERS_FOR(ast_sdp_offerer_modify_cb, offerer_modify_cb);<br>+DEFINE_GETTERS_SETTERS_FOR(ast_sdp_offerer_config_cb, offerer_config_cb);<br>+DEFINE_GETTERS_SETTERS_FOR(ast_sdp_preapply_cb, preapply_cb);<br>+DEFINE_GETTERS_SETTERS_FOR(ast_sdp_postapply_cb, postapply_cb);<br> DEFINE_GETTERS_SETTERS_FOR(unsigned int, rtp_symmetric);<br> DEFINE_GETTERS_SETTERS_FOR(unsigned int, udptl_symmetric);<br> DEFINE_GETTERS_SETTERS_FOR(enum ast_t38_ec_modes, udptl_error_correction);<br>@@ -71,6 +78,7 @@<br> DEFINE_GETTERS_SETTERS_FOR(unsigned int, cos_audio);<br> DEFINE_GETTERS_SETTERS_FOR(unsigned int, tos_video);<br> DEFINE_GETTERS_SETTERS_FOR(unsigned int, cos_video);<br>+DEFINE_GETTERS_SETTERS_FOR(unsigned int, max_streams);<br> DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_dtmf, dtmf);<br> DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_ice, ice);<br> DEFINE_GETTERS_SETTERS_FOR(enum ast_sdp_options_impl, impl);<br>@@ -110,12 +118,87 @@<br> }<br> }<br> <br>+struct ast_format_cap *ast_sdp_options_get_format_cap_type(const struct ast_sdp_options *options,<br>+ enum ast_media_type type)<br>+{<br>+ struct ast_format_cap *cap = NULL;<br>+<br>+ switch (type) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ cap = options->caps[type];<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_END:<br>+ break;<br>+ }<br>+ return cap;<br>+}<br>+<br>+void ast_sdp_options_set_format_cap_type(struct ast_sdp_options *options,<br>+ enum ast_media_type type, struct ast_format_cap *cap)<br>+{<br>+ switch (type) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ ao2_cleanup(options->caps[type]);<br>+ options->caps[type] = NULL;<br>+ if (cap && !ast_format_cap_empty(cap)) {<br>+ ao2_ref(cap, +1);<br>+ options->caps[type] = cap;<br>+ }<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_END:<br>+ break;<br>+ }<br>+}<br>+<br>+void ast_sdp_options_set_format_caps(struct ast_sdp_options *options,<br>+ struct ast_format_cap *cap)<br>+{<br>+ enum ast_media_type type;<br>+<br>+ for (type = AST_MEDIA_TYPE_UNKNOWN; type < AST_MEDIA_TYPE_END; ++type) {<br>+ ao2_cleanup(options->caps[type]);<br>+ options->caps[type] = NULL;<br>+ }<br>+<br>+ if (!cap || ast_format_cap_empty(cap)) {<br>+ return;<br>+ }<br>+<br>+ for (type = AST_MEDIA_TYPE_UNKNOWN + 1; type < AST_MEDIA_TYPE_END; ++type) {<br>+ struct ast_format_cap *type_cap;<br>+<br>+ type_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+ if (!type_cap) {<br>+ continue;<br>+ }<br>+<br>+ ast_format_cap_set_framing(type_cap, ast_format_cap_get_framing(cap));<br>+ if (ast_format_cap_append_from_cap(type_cap, cap, type)<br>+ || ast_format_cap_empty(type_cap)) {<br>+ ao2_ref(type_cap, -1);<br>+ continue;<br>+ }<br>+<br>+ /* This takes the allocation reference */<br>+ options->caps[type] = type_cap;<br>+ }<br>+}<br>+<br> static void set_defaults(struct ast_sdp_options *options)<br> {<br> options->dtmf = DEFAULT_DTMF;<br> options->ice = DEFAULT_ICE;<br> options->impl = DEFAULT_IMPL;<br> options->encryption = DEFAULT_ENCRYPTION;<br>+ options->max_streams = DEFAULT_MAX_STREAMS;<br> }<br> <br> struct ast_sdp_options *ast_sdp_options_alloc(void)<br>@@ -138,6 +221,15 @@<br> <br> void ast_sdp_options_free(struct ast_sdp_options *options)<br> {<br>+ enum ast_media_type type;<br>+<br>+ if (!options) {<br>+ return;<br>+ }<br>+<br>+ for (type = AST_MEDIA_TYPE_UNKNOWN; type < AST_MEDIA_TYPE_END; ++type) {<br>+ ao2_cleanup(options->caps[type]);<br>+ }<br> ast_string_field_free_memory(options);<br> ast_free(options);<br> }<br>diff --git a/main/sdp_private.h b/main/sdp_private.h<br>index 62228a5..48bedc8 100644<br>--- a/main/sdp_private.h<br>+++ b/main/sdp_private.h<br>@@ -24,7 +24,7 @@<br> <br> struct ast_sdp_options {<br> AST_DECLARE_STRING_FIELDS(<br>- /*! Media address to use in SDP */<br>+ /*! Media address to advertise in SDP session c= line */<br> AST_STRING_FIELD(media_address);<br> /*! Optional address of the interface media should use. */<br> AST_STRING_FIELD(interface_address);<br>@@ -37,12 +37,25 @@<br> );<br> /*! Scheduler context for the media stream types (Mainly for RTP) */<br> struct ast_sched_context *sched[AST_MEDIA_TYPE_END];<br>+ /*! Capabilities to create new streams of the indexed media type. */<br>+ struct ast_format_cap *caps[AST_MEDIA_TYPE_END];<br>+ /*! User supplied context data pointer for the SDP state. */<br>+ void *state_context;<br>+ /*! Modify negotiated topology before create answer SDP callback. */<br>+ ast_sdp_answerer_modify_cb answerer_modify_cb;<br>+ /*! Modify proposed topology before create offer SDP callback. */<br>+ ast_sdp_offerer_modify_cb offerer_modify_cb;<br>+ /*! Configure proposed topology extra stream options before create offer SDP callback. */<br>+ ast_sdp_offerer_config_cb offerer_config_cb;<br>+ /*! Negotiated topology is about to be applied callback. */<br>+ ast_sdp_preapply_cb preapply_cb;<br>+ /*! Negotiated topology was just applied callback. */<br>+ ast_sdp_postapply_cb postapply_cb;<br> struct {<br> unsigned int rtp_symmetric:1;<br> unsigned int udptl_symmetric:1;<br> unsigned int rtp_ipv6:1;<br> unsigned int g726_non_standard:1;<br>- unsigned int locally_held:1;<br> unsigned int rtcp_mux:1;<br> unsigned int ssrc:1;<br> };<br>@@ -52,6 +65,8 @@<br> unsigned int tos_video;<br> unsigned int cos_video;<br> unsigned int udptl_far_max_datagram;<br>+ /*! Maximum number of streams to allow. */<br>+ unsigned int max_streams;<br> };<br> enum ast_sdp_options_dtmf dtmf;<br> enum ast_sdp_options_ice ice;<br>diff --git a/main/sdp_state.c b/main/sdp_state.c<br>index 99421ad..330140c 100644<br>--- a/main/sdp_state.c<br>+++ b/main/sdp_state.c<br>@@ -85,11 +85,38 @@<br> };<br> /*! An explicit connection address for this stream */<br> struct ast_sockaddr connection_address;<br>- /*! Whether this stream is held or not */<br>- unsigned int locally_held;<br>+ /*!<br>+ * \brief Stream is on hold by remote side<br>+ *<br>+ * \note This flag is never set on the<br>+ * sdp_state->proposed_capabilities->streams states. This is useful<br>+ * when the remote sends us a reINVITE with a deferred SDP to place<br>+ * us on and off of hold.<br>+ */<br>+ unsigned int remotely_held:1;<br>+ /*! Stream is on hold by local side */<br>+ unsigned int locally_held:1;<br> /*! UDPTL session parameters */<br> struct ast_control_t38_parameters t38_local_params;<br> };<br>+<br>+static int sdp_is_stream_type_supported(enum ast_media_type type)<br>+{<br>+ int is_supported = 0;<br>+<br>+ switch (type) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ is_supported = 1;<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ case AST_MEDIA_TYPE_END:<br>+ break;<br>+ }<br>+ return is_supported;<br>+}<br> <br> static void sdp_state_rtp_destroy(void *obj)<br> {<br>@@ -135,8 +162,6 @@<br> struct ast_stream_topology *topology;<br> /*! Additional information about the streams */<br> struct sdp_state_streams streams;<br>- /*! An explicit global connection address */<br>- struct ast_sockaddr connection_address;<br> };<br> <br> static void sdp_state_capabilities_free(struct sdp_state_capabilities *capabilities)<br>@@ -269,30 +294,80 @@<br> return udptl;<br> }<br> <br>+static struct ast_stream *merge_local_stream(const struct ast_sdp_options *options,<br>+ const struct ast_stream *update);<br>+<br> static struct sdp_state_capabilities *sdp_initialize_state_capabilities(const struct ast_stream_topology *topology,<br> const struct ast_sdp_options *options)<br> {<br> struct sdp_state_capabilities *capabilities;<br>- int i;<br>+ struct ast_stream *stream;<br>+ unsigned int topology_count;<br>+ unsigned int max_streams;<br>+ unsigned int idx;<br> <br> capabilities = ast_calloc(1, sizeof(*capabilities));<br> if (!capabilities) {<br> return NULL;<br> }<br> <br>- capabilities->topology = ast_stream_topology_clone(topology);<br>+ capabilities->topology = ast_stream_topology_alloc();<br> if (!capabilities->topology) {<br> sdp_state_capabilities_free(capabilities);<br> return NULL;<br> }<br> <br>- if (AST_VECTOR_INIT(&capabilities->streams, ast_stream_topology_get_count(topology))) {<br>+ max_streams = ast_sdp_options_get_max_streams(options);<br>+ if (topology) {<br>+ topology_count = ast_stream_topology_get_count(topology);<br>+ } else {<br>+ topology_count = 0;<br>+ }<br>+<br>+ /* Gather acceptable streams from the initial topology */<br>+ for (idx = 0; idx < topology_count; ++idx) {<br>+ stream = ast_stream_topology_get_stream(topology, idx);<br>+ if (!sdp_is_stream_type_supported(ast_stream_get_type(stream))) {<br>+ /* Delete the unsupported stream from the initial topology */<br>+ continue;<br>+ }<br>+ if (max_streams <= ast_stream_topology_get_count(capabilities->topology)) {<br>+ /* Cannot support any more streams */<br>+ break;<br>+ }<br>+<br>+ stream = merge_local_stream(options, stream);<br>+ if (!stream) {<br>+ sdp_state_capabilities_free(capabilities);<br>+ return NULL;<br>+ }<br>+<br>+ if (ast_stream_topology_append_stream(capabilities->topology, stream) < 0) {<br>+ ast_stream_free(stream);<br>+ sdp_state_capabilities_free(capabilities);<br>+ return NULL;<br>+ }<br>+ }<br>+<br>+ /*<br>+ * Remove trailing declined streams from the initial built topology.<br>+ * No need to waste space in the SDP with these unused slots.<br>+ */<br>+ for (idx = ast_stream_topology_get_count(capabilities->topology); idx--;) {<br>+ stream = ast_stream_topology_get_stream(capabilities->topology, idx);<br>+ if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) {<br>+ break;<br>+ }<br>+ ast_stream_topology_del_stream(capabilities->topology, idx);<br>+ }<br>+<br>+ topology_count = ast_stream_topology_get_count(capabilities->topology);<br>+ if (AST_VECTOR_INIT(&capabilities->streams, topology_count)) {<br> sdp_state_capabilities_free(capabilities);<br> return NULL;<br> }<br>- ast_sockaddr_setnull(&capabilities->connection_address);<br> <br>- for (i = 0; i < ast_stream_topology_get_count(topology); ++i) {<br>+ for (idx = 0; idx < topology_count; ++idx) {<br> struct sdp_state_stream *state_stream;<br> <br> state_stream = ast_calloc(1, sizeof(*state_stream));<br>@@ -301,32 +376,34 @@<br> return NULL;<br> }<br> <br>- state_stream->type = ast_stream_get_type(ast_stream_topology_get_stream(topology, i));<br>- switch (state_stream->type) {<br>- case AST_MEDIA_TYPE_AUDIO:<br>- case AST_MEDIA_TYPE_VIDEO:<br>- state_stream->rtp = create_rtp(options, state_stream->type);<br>- if (!state_stream->rtp) {<br>- sdp_state_stream_free(state_stream);<br>- sdp_state_capabilities_free(capabilities);<br>- return NULL;<br>+ stream = ast_stream_topology_get_stream(capabilities->topology, idx);<br>+ state_stream->type = ast_stream_get_type(stream);<br>+ if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) {<br>+ switch (state_stream->type) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ state_stream->rtp = create_rtp(options, state_stream->type);<br>+ if (!state_stream->rtp) {<br>+ sdp_state_stream_free(state_stream);<br>+ sdp_state_capabilities_free(capabilities);<br>+ return NULL;<br>+ }<br>+ break;<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ state_stream->udptl = create_udptl(options);<br>+ if (!state_stream->udptl) {<br>+ sdp_state_stream_free(state_stream);<br>+ sdp_state_capabilities_free(capabilities);<br>+ return NULL;<br>+ }<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ case AST_MEDIA_TYPE_END:<br>+ /* Unsupported stream type already handled earlier */<br>+ ast_assert(0);<br>+ break;<br> }<br>- break;<br>- case AST_MEDIA_TYPE_IMAGE:<br>- state_stream->udptl = create_udptl(options);<br>- if (!state_stream->udptl) {<br>- sdp_state_stream_free(state_stream);<br>- sdp_state_capabilities_free(capabilities);<br>- return NULL;<br>- }<br>- break;<br>- case AST_MEDIA_TYPE_UNKNOWN:<br>- case AST_MEDIA_TYPE_TEXT:<br>- case AST_MEDIA_TYPE_END:<br>- ast_assert(0);<br>- sdp_state_stream_free(state_stream);<br>- sdp_state_capabilities_free(capabilities);<br>- return NULL;<br> }<br> <br> if (AST_VECTOR_APPEND(&capabilities->streams, state_stream)) {<br>@@ -350,12 +427,12 @@<br> * is allocated, this topology is used to create the proposed_capabilities.<br> *<br> * If we are the SDP offerer, then the proposed_capabilities are what are used<br>- * to generate the SDP offer. When the SDP answer arrives, the proposed capabilities<br>- * are merged with the SDP answer to create the negotiated capabilities.<br>+ * to generate the offer SDP. When the answer SDP arrives, the proposed capabilities<br>+ * are merged with the answer SDP to create the negotiated capabilities.<br> *<br>- * If we are the SDP answerer, then the incoming SDP offer is merged with our<br>+ * If we are the SDP answerer, then the incoming offer SDP is merged with our<br> * proposed capabilities to to create the negotiated capabilities. These negotiated<br>- * capabilities are what we send in our SDP answer.<br>+ * capabilities are what we send in our answer SDP.<br> *<br> * Any changes that a user of the API performs will occur on the proposed capabilities.<br> * The negotiated capabilities are only altered based on actual SDP negotiation. This is<br>@@ -367,17 +444,33 @@<br> struct sdp_state_capabilities *negotiated_capabilities;<br> /*! Proposed capabilities */<br> struct sdp_state_capabilities *proposed_capabilities;<br>- /*! Local SDP. Generated via the options and currently negotiated/proposed capabilities. */<br>+ /*!<br>+ * \brief New topology waiting to be merged.<br>+ *<br>+ * \details<br>+ * Repeated topology updates are merged into each other here until<br>+ * negotiations are restarted and we create an offer.<br>+ */<br>+ struct ast_stream_topology *pending_topology_update;<br>+ /*! Local SDP. Generated via the options and negotiated/proposed capabilities. */<br> struct ast_sdp *local_sdp;<br>+ /*! Saved remote SDP */<br>+ struct ast_sdp *remote_sdp;<br> /*! SDP options. Configured options beyond media capabilities. */<br> struct ast_sdp_options *options;<br> /*! Translator that puts SDPs into the expected representation */<br> struct ast_sdp_translator *translator;<br>+ /*! An explicit global connection address */<br>+ struct ast_sockaddr connection_address;<br> /*! The role that we occupy in SDP negotiation */<br> enum ast_sdp_role role;<br>+ /*! TRUE if all streams on hold by local side */<br>+ unsigned int locally_held:1;<br>+ /*! TRUE if the remote offer resulted in all streams being declined. */<br>+ unsigned int remote_offer_rejected:1;<br> };<br> <br>-struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *streams,<br>+struct ast_sdp_state *ast_sdp_state_alloc(struct ast_stream_topology *topology,<br> struct ast_sdp_options *options)<br> {<br> struct ast_sdp_state *sdp_state;<br>@@ -395,7 +488,7 @@<br> return NULL;<br> }<br> <br>- sdp_state->proposed_capabilities = sdp_initialize_state_capabilities(streams, options);<br>+ sdp_state->proposed_capabilities = sdp_initialize_state_capabilities(topology, options);<br> if (!sdp_state->proposed_capabilities) {<br> ast_sdp_state_free(sdp_state);<br> return NULL;<br>@@ -414,10 +507,215 @@<br> <br> sdp_state_capabilities_free(sdp_state->negotiated_capabilities);<br> sdp_state_capabilities_free(sdp_state->proposed_capabilities);<br>- ast_sdp_free(sdp_state->local_sdp);<br>+ ao2_cleanup(sdp_state->local_sdp);<br>+ ao2_cleanup(sdp_state->remote_sdp);<br> ast_sdp_options_free(sdp_state->options);<br> ast_sdp_translator_free(sdp_state->translator);<br> ast_free(sdp_state);<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Allow a configured callback to alter the new negotiated joint topology.<br>+ * \since 15.0.0<br>+ *<br>+ * \details<br>+ * The callback can alter topology stream names, formats, or decline streams.<br>+ *<br>+ * \param sdp_state<br>+ * \param topology Joint topology that we intend to generate the answer SDP.<br>+ *<br>+ * \return Nothing<br>+ */<br>+static void sdp_state_cb_answerer_modify_topology(const struct ast_sdp_state *sdp_state,<br>+ struct ast_stream_topology *topology)<br>+{<br>+ ast_sdp_answerer_modify_cb cb;<br>+<br>+ cb = ast_sdp_options_get_answerer_modify_cb(sdp_state->options);<br>+ if (cb) {<br>+ void *context;<br>+ const struct ast_stream_topology *neg_topology;/*!< Last negotiated topology */<br>+#ifdef AST_DEVMODE<br>+ struct ast_stream *stream;<br>+ int idx;<br>+ enum ast_media_type type[ast_stream_topology_get_count(topology)];<br>+ enum ast_stream_state state[ast_stream_topology_get_count(topology)];<br>+<br>+ /*<br>+ * Save stream types and states to validate that they don't<br>+ * get changed unexpectedly.<br>+ */<br>+ for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) {<br>+ stream = ast_stream_topology_get_stream(topology, idx);<br>+ type[idx] = ast_stream_get_type(stream);<br>+ state[idx] = ast_stream_get_state(stream);<br>+ }<br>+#endif<br>+<br>+ context = ast_sdp_options_get_state_context(sdp_state->options);<br>+ neg_topology = sdp_state->negotiated_capabilities<br>+ ? sdp_state->negotiated_capabilities->topology : NULL;<br>+ cb(context, neg_topology, topology);<br>+<br>+#ifdef AST_DEVMODE<br>+ for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) {<br>+ stream = ast_stream_topology_get_stream(topology, idx);<br>+<br>+ /* Check that active streams have at least one format */<br>+ ast_assert(ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED<br>+ || (ast_stream_get_formats(stream)<br>+ && ast_format_cap_count(ast_stream_get_formats(stream))));<br>+<br>+ /* Check that stream types didn't change. */<br>+ ast_assert(type[idx] == ast_stream_get_type(stream));<br>+<br>+ /* Check that streams didn't get resurected. */<br>+ ast_assert(state[idx] != AST_STREAM_STATE_REMOVED<br>+ || ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED);<br>+ }<br>+#endif<br>+ }<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Allow a configured callback to alter the merged local topology.<br>+ * \since 15.0.0<br>+ *<br>+ * \details<br>+ * The callback can modify streams in the merged topology. The<br>+ * callback can decline, add/remove/update formats, or rename<br>+ * streams. Changing anything else on the streams is likely to not<br>+ * end well.<br>+ *<br>+ * \param sdp_state<br>+ * \param topology Merged topology that we intend to generate the offer SDP.<br>+ *<br>+ * \return Nothing<br>+ */<br>+static void sdp_state_cb_offerer_modify_topology(const struct ast_sdp_state *sdp_state,<br>+ struct ast_stream_topology *topology)<br>+{<br>+ ast_sdp_offerer_modify_cb cb;<br>+<br>+ cb = ast_sdp_options_get_offerer_modify_cb(sdp_state->options);<br>+ if (cb) {<br>+ void *context;<br>+ const struct ast_stream_topology *neg_topology;/*!< Last negotiated topology */<br>+<br>+ context = ast_sdp_options_get_state_context(sdp_state->options);<br>+ neg_topology = sdp_state->negotiated_capabilities<br>+ ? sdp_state->negotiated_capabilities->topology : NULL;<br>+ cb(context, neg_topology, topology);<br>+<br>+#ifdef AST_DEVMODE<br>+ {<br>+ struct ast_stream *stream;<br>+ int idx;<br>+<br>+ /* Check that active streams have at least one format */<br>+ for (idx = 0; idx < ast_stream_topology_get_count(topology); ++idx) {<br>+ stream = ast_stream_topology_get_stream(topology, idx);<br>+ ast_assert(ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED<br>+ || (ast_stream_get_formats(stream)<br>+ && ast_format_cap_count(ast_stream_get_formats(stream))));<br>+ }<br>+ }<br>+#endif<br>+ }<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Allow a configured callback to configure the merged local topology.<br>+ * \since 15.0.0<br>+ *<br>+ * \details<br>+ * The callback can configure other parameters associated with each<br>+ * active stream on the topology. The callback can call several SDP<br>+ * API calls to configure the proposed capabilities of the streams<br>+ * before we create the offer SDP. For example, the callback could<br>+ * configure a stream specific connection address, T.38 parameters,<br>+ * RTP instance, or UDPTL instance parameters.<br>+ *<br>+ * \param sdp_state<br>+ * \param topology Merged topology that we intend to generate the offer SDP.<br>+ *<br>+ * \return Nothing<br>+ */<br>+static void sdp_state_cb_offerer_config_topology(const struct ast_sdp_state *sdp_state,<br>+ const struct ast_stream_topology *topology)<br>+{<br>+ ast_sdp_offerer_config_cb cb;<br>+<br>+ cb = ast_sdp_options_get_offerer_config_cb(sdp_state->options);<br>+ if (cb) {<br>+ void *context;<br>+<br>+ context = ast_sdp_options_get_state_context(sdp_state->options);<br>+ cb(context, topology);<br>+ }<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Call any registered pre-apply topology callback.<br>+ * \since 15.0.0<br>+ *<br>+ * \param sdp_state<br>+ * \param topology<br>+ *<br>+ * \return Nothing<br>+ */<br>+static void sdp_state_cb_preapply_topology(const struct ast_sdp_state *sdp_state,<br>+ const struct ast_stream_topology *topology)<br>+{<br>+ ast_sdp_preapply_cb cb;<br>+<br>+ cb = ast_sdp_options_get_preapply_cb(sdp_state->options);<br>+ if (cb) {<br>+ void *context;<br>+<br>+ context = ast_sdp_options_get_state_context(sdp_state->options);<br>+ cb(context, topology);<br>+ }<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Call any registered post-apply topology callback.<br>+ * \since 15.0.0<br>+ *<br>+ * \param sdp_state<br>+ * \param topology<br>+ *<br>+ * \return Nothing<br>+ */<br>+static void sdp_state_cb_postapply_topology(const struct ast_sdp_state *sdp_state,<br>+ const struct ast_stream_topology *topology)<br>+{<br>+ ast_sdp_postapply_cb cb;<br>+<br>+ cb = ast_sdp_options_get_postapply_cb(sdp_state->options);<br>+ if (cb) {<br>+ void *context;<br>+<br>+ context = ast_sdp_options_get_state_context(sdp_state->options);<br>+ cb(context, topology);<br>+ }<br>+}<br>+<br>+static const struct sdp_state_capabilities *sdp_state_get_joint_capabilities(<br>+ const struct ast_sdp_state *sdp_state)<br>+{<br>+ ast_assert(sdp_state != NULL);<br>+<br>+ if (sdp_state->negotiated_capabilities) {<br>+ return sdp_state->negotiated_capabilities;<br>+ }<br>+<br>+ return sdp_state->proposed_capabilities;<br> }<br> <br> static struct sdp_state_stream *sdp_state_get_stream(const struct ast_sdp_state *sdp_state, int stream_index)<br>@@ -427,6 +725,18 @@<br> }<br> <br> return AST_VECTOR_GET(&sdp_state->proposed_capabilities->streams, stream_index);<br>+}<br>+<br>+static struct sdp_state_stream *sdp_state_get_joint_stream(const struct ast_sdp_state *sdp_state, int stream_index)<br>+{<br>+ const struct sdp_state_capabilities *capabilities;<br>+<br>+ capabilities = sdp_state_get_joint_capabilities(sdp_state);<br>+ if (AST_VECTOR_SIZE(&capabilities->streams) <= stream_index) {<br>+ return NULL;<br>+ }<br>+<br>+ return AST_VECTOR_GET(&capabilities->streams, stream_index);<br> }<br> <br> struct ast_rtp_instance *ast_sdp_state_get_rtp_instance(<br>@@ -468,7 +778,56 @@<br> {<br> ast_assert(sdp_state != NULL);<br> <br>- return &sdp_state->proposed_capabilities->connection_address;<br>+ return &sdp_state->connection_address;<br>+}<br>+<br>+static int sdp_state_stream_get_connection_address(const struct ast_sdp_state *sdp_state,<br>+ struct sdp_state_stream *stream_state, struct ast_sockaddr *address)<br>+{<br>+ ast_assert(sdp_state != NULL);<br>+ ast_assert(stream_state != NULL);<br>+ ast_assert(address != NULL);<br>+<br>+ /* If an explicit connection address has been provided for the stream return it */<br>+ if (!ast_sockaddr_isnull(&stream_state->connection_address)) {<br>+ ast_sockaddr_copy(address, &stream_state->connection_address);<br>+ return 0;<br>+ }<br>+<br>+ switch (stream_state->type) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ if (!stream_state->rtp->instance) {<br>+ return -1;<br>+ }<br>+ ast_rtp_instance_get_local_address(stream_state->rtp->instance, address);<br>+ break;<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ if (!stream_state->udptl->instance) {<br>+ return -1;<br>+ }<br>+ ast_udptl_get_us(stream_state->udptl->instance, address);<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ case AST_MEDIA_TYPE_END:<br>+ return -1;<br>+ }<br>+<br>+ if (ast_sockaddr_isnull(address)) {<br>+ /* No address is set on the stream state. */<br>+ return -1;<br>+ }<br>+<br>+ /* If an explicit global connection address is set use it here for the IP part */<br>+ if (!ast_sockaddr_isnull(&sdp_state->connection_address)) {<br>+ int port = ast_sockaddr_port(address);<br>+<br>+ ast_sockaddr_copy(address, &sdp_state->connection_address);<br>+ ast_sockaddr_set_port(address, port);<br>+ }<br>+<br>+ return 0;<br> }<br> <br> int ast_sdp_state_get_stream_connection_address(const struct ast_sdp_state *sdp_state,<br>@@ -484,48 +843,16 @@<br> return -1;<br> }<br> <br>- /* If an explicit connection address has been provided for the stream return it */<br>- if (!ast_sockaddr_isnull(&stream_state->connection_address)) {<br>- ast_sockaddr_copy(address, &stream_state->connection_address);<br>- return 0;<br>- }<br>-<br>- switch (ast_stream_get_type(ast_stream_topology_get_stream(sdp_state->proposed_capabilities->topology,<br>- stream_index))) {<br>- case AST_MEDIA_TYPE_AUDIO:<br>- case AST_MEDIA_TYPE_VIDEO:<br>- ast_rtp_instance_get_local_address(stream_state->rtp->instance, address);<br>- break;<br>- case AST_MEDIA_TYPE_IMAGE:<br>- ast_udptl_get_us(stream_state->udptl->instance, address);<br>- break;<br>- case AST_MEDIA_TYPE_UNKNOWN:<br>- case AST_MEDIA_TYPE_TEXT:<br>- case AST_MEDIA_TYPE_END:<br>- return -1;<br>- }<br>-<br>- /* If an explicit global connection address is set use it here for the IP part */<br>- if (!ast_sockaddr_isnull(&sdp_state->proposed_capabilities->connection_address)) {<br>- int port = ast_sockaddr_port(address);<br>-<br>- ast_sockaddr_copy(address, &sdp_state->proposed_capabilities->connection_address);<br>- ast_sockaddr_set_port(address, port);<br>- }<br>-<br>- return 0;<br>+ return sdp_state_stream_get_connection_address(sdp_state, stream_state, address);<br> }<br> <br> const struct ast_stream_topology *ast_sdp_state_get_joint_topology(<br> const struct ast_sdp_state *sdp_state)<br> {<br>- ast_assert(sdp_state != NULL);<br>+ const struct sdp_state_capabilities *capabilities;<br> <br>- if (sdp_state->negotiated_capabilities) {<br>- return sdp_state->negotiated_capabilities->topology;<br>- }<br>-<br>- return sdp_state->proposed_capabilities->topology;<br>+ capabilities = sdp_state_get_joint_capabilities(sdp_state);<br>+ return capabilities->topology;<br> }<br> <br> const struct ast_stream_topology *ast_sdp_state_get_local_topology(<br>@@ -544,50 +871,76 @@<br> return sdp_state->options;<br> }<br> <br>+static struct ast_stream *decline_stream(enum ast_media_type type, const char *name)<br>+{<br>+ struct ast_stream *stream;<br>+<br>+ if (!name) {<br>+ name = ast_codec_media_type2str(type);<br>+ }<br>+ stream = ast_stream_alloc(name, type);<br>+ if (!stream) {<br>+ return NULL;<br>+ }<br>+ ast_stream_set_state(stream, AST_STREAM_STATE_REMOVED);<br>+ return stream;<br>+}<br>+<br> /*!<br>- * \brief Merge two streams into a joint stream.<br>+ * \brief Merge an update stream into a local stream.<br> *<br>- * \param local Our local stream<br>- * \param remote A remote stream<br>+ * \param options SDP Options<br>+ * \param update An updated stream<br> *<br> * \retval NULL An error occurred<br> * \retval non-NULL The joint stream created<br> */<br>-static struct ast_stream *merge_streams(const struct ast_stream *local,<br>- const struct ast_stream *remote)<br>+static struct ast_stream *merge_local_stream(const struct ast_sdp_options *options,<br>+ const struct ast_stream *update)<br> {<br> struct ast_stream *joint_stream;<br> struct ast_format_cap *joint_cap;<br>- struct ast_format_cap *local_cap;<br>- struct ast_format_cap *remote_cap;<br>- struct ast_str *local_buf = ast_str_alloca(128);<br>- struct ast_str *remote_buf = ast_str_alloca(128);<br>- struct ast_str *joint_buf = ast_str_alloca(128);<br>-<br>- joint_stream = ast_stream_alloc(ast_codec_media_type2str(ast_stream_get_type(remote)),<br>- ast_stream_get_type(remote));<br>- if (!joint_stream) {<br>- return NULL;<br>- }<br>+ struct ast_format_cap *allowed_cap;<br>+ struct ast_format_cap *update_cap;<br>+ enum ast_stream_state joint_state;<br> <br> joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br> if (!joint_cap) {<br>- ast_stream_free(joint_stream);<br> return NULL;<br> }<br> <br>- local_cap = ast_stream_get_formats(local);<br>- remote_cap = ast_stream_get_formats(remote);<br>+ update_cap = ast_stream_get_formats(update);<br>+ allowed_cap = ast_sdp_options_get_format_cap_type(options,<br>+ ast_stream_get_type(update));<br>+ if (allowed_cap && update_cap) {<br>+ struct ast_str *allowed_buf = ast_str_alloca(128);<br>+ struct ast_str *update_buf = ast_str_alloca(128);<br>+ struct ast_str *joint_buf = ast_str_alloca(128);<br> <br>- ast_format_cap_get_compatible(local_cap, remote_cap, joint_cap);<br>+ ast_format_cap_get_compatible(allowed_cap, update_cap, joint_cap);<br>+ ast_debug(3,<br>+ "Filtered update '%s' with allowed '%s' to get joint '%s'. Joint has %zu formats\n",<br>+ ast_format_cap_get_names(update_cap, &update_buf),<br>+ ast_format_cap_get_names(allowed_cap, &allowed_buf),<br>+ ast_format_cap_get_names(joint_cap, &joint_buf),<br>+ ast_format_cap_count(joint_cap));<br>+ }<br> <br>- ast_debug(3, "Combined local '%s' with remote '%s' to get joint '%s'. Joint has %zu formats\n",<br>- ast_format_cap_get_names(local_cap, &local_buf),<br>- ast_format_cap_get_names(remote_cap, &remote_buf),<br>- ast_format_cap_get_names(joint_cap, &joint_buf),<br>- ast_format_cap_count(joint_cap));<br>+ /* Determine the joint stream state */<br>+ joint_state = AST_STREAM_STATE_REMOVED;<br>+ if (ast_stream_get_state(update) != AST_STREAM_STATE_REMOVED<br>+ && ast_format_cap_count(joint_cap)) {<br>+ joint_state = AST_STREAM_STATE_SENDRECV;<br>+ }<br> <br>- ast_stream_set_formats(joint_stream, joint_cap);<br>+ joint_stream = ast_stream_alloc(ast_stream_get_name(update),<br>+ ast_stream_get_type(update));<br>+ if (joint_stream) {<br>+ ast_stream_set_state(joint_stream, joint_state);<br>+ if (joint_state != AST_STREAM_STATE_REMOVED) {<br>+ ast_stream_set_formats(joint_stream, joint_cap);<br>+ }<br>+ }<br> <br> ao2_ref(joint_cap, -1);<br> <br>@@ -595,103 +948,1025 @@<br> }<br> <br> /*!<br>- * \brief Get a local stream that corresponds with a remote stream.<br>+ * \brief Merge a remote stream into a local stream.<br> *<br>- * \param local The local topology<br>- * \param media_type The type of stream we are looking for<br>- * \param[in,out] media_indices Keeps track of where to start searching in the topology<br>+ * \param sdp_state<br>+ * \param local Our local stream (NULL if creating new stream)<br>+ * \param locally_held Nonzero if the local stream is held<br>+ * \param remote A remote stream<br> *<br>- * \retval -1 No corresponding stream found<br>- * \retval index The corresponding stream index<br>+ * \retval NULL An error occurred<br>+ * \retval non-NULL The joint stream created<br> */<br>-static int get_corresponding_index(const struct ast_stream_topology *local,<br>- enum ast_media_type media_type, int *media_indices)<br>+static struct ast_stream *merge_remote_stream(const struct ast_sdp_state *sdp_state,<br>+ const struct ast_stream *local, unsigned int locally_held,<br>+ const struct ast_stream *remote)<br> {<br>- int i;<br>+ struct ast_stream *joint_stream;<br>+ struct ast_format_cap *joint_cap;<br>+ struct ast_format_cap *local_cap;<br>+ struct ast_format_cap *remote_cap;<br>+ const char *joint_name;<br>+ enum ast_stream_state joint_state;<br>+ enum ast_stream_state remote_state;<br> <br>- for (i = media_indices[media_type]; i < ast_stream_topology_get_count(local); ++i) {<br>- struct ast_stream *candidate;<br>+ joint_cap = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+ if (!joint_cap) {<br>+ return NULL;<br>+ }<br> <br>- candidate = ast_stream_topology_get_stream(local, i);<br>- if (ast_stream_get_type(candidate) == media_type) {<br>- media_indices[media_type] = i + 1;<br>- return i;<br>+ remote_cap = ast_stream_get_formats(remote);<br>+ if (local) {<br>+ local_cap = ast_stream_get_formats(local);<br>+ } else {<br>+ local_cap = ast_sdp_options_get_format_cap_type(sdp_state->options,<br>+ ast_stream_get_type(remote));<br>+ }<br>+ if (local_cap && remote_cap) {<br>+ struct ast_str *local_buf = ast_str_alloca(128);<br>+ struct ast_str *remote_buf = ast_str_alloca(128);<br>+ struct ast_str *joint_buf = ast_str_alloca(128);<br>+<br>+ ast_format_cap_get_compatible(local_cap, remote_cap, joint_cap);<br>+ ast_debug(3,<br>+ "Combined local '%s' with remote '%s' to get joint '%s'. Joint has %zu formats\n",<br>+ ast_format_cap_get_names(local_cap, &local_buf),<br>+ ast_format_cap_get_names(remote_cap, &remote_buf),<br>+ ast_format_cap_get_names(joint_cap, &joint_buf),<br>+ ast_format_cap_count(joint_cap));<br>+ }<br>+<br>+ /* Determine the joint stream state */<br>+ remote_state = ast_stream_get_state(remote);<br>+ joint_state = AST_STREAM_STATE_REMOVED;<br>+ if ((!local || ast_stream_get_state(local) != AST_STREAM_STATE_REMOVED)<br>+ && ast_format_cap_count(joint_cap)) {<br>+ if (sdp_state->locally_held || locally_held) {<br>+ switch (remote_state) {<br>+ case AST_STREAM_STATE_REMOVED:<br>+ break;<br>+ case AST_STREAM_STATE_INACTIVE:<br>+ joint_state = AST_STREAM_STATE_INACTIVE;<br>+ break;<br>+ case AST_STREAM_STATE_SENDRECV:<br>+ joint_state = AST_STREAM_STATE_SENDONLY;<br>+ break;<br>+ case AST_STREAM_STATE_SENDONLY:<br>+ joint_state = AST_STREAM_STATE_INACTIVE;<br>+ break;<br>+ case AST_STREAM_STATE_RECVONLY:<br>+ joint_state = AST_STREAM_STATE_SENDONLY;<br>+ break;<br>+ }<br>+ } else {<br>+ switch (remote_state) {<br>+ case AST_STREAM_STATE_REMOVED:<br>+ break;<br>+ case AST_STREAM_STATE_INACTIVE:<br>+ joint_state = AST_STREAM_STATE_RECVONLY;<br>+ break;<br>+ case AST_STREAM_STATE_SENDRECV:<br>+ joint_state = AST_STREAM_STATE_SENDRECV;<br>+ break;<br>+ case AST_STREAM_STATE_SENDONLY:<br>+ joint_state = AST_STREAM_STATE_RECVONLY;<br>+ break;<br>+ case AST_STREAM_STATE_RECVONLY:<br>+ joint_state = AST_STREAM_STATE_SENDRECV;<br>+ break;<br>+ }<br> }<br> }<br> <br>- /* No stream of the type left in the topology */<br>- media_indices[media_type] = i;<br>- return -1;<br>+ if (local) {<br>+ joint_name = ast_stream_get_name(local);<br>+ } else {<br>+ joint_name = ast_codec_media_type2str(ast_stream_get_type(remote));<br>+ }<br>+ joint_stream = ast_stream_alloc(joint_name, ast_stream_get_type(remote));<br>+ if (joint_stream) {<br>+ ast_stream_set_state(joint_stream, joint_state);<br>+ if (joint_state != AST_STREAM_STATE_REMOVED) {<br>+ ast_stream_set_formats(joint_stream, joint_cap);<br>+ }<br>+ }<br>+<br>+ ao2_ref(joint_cap, -1);<br>+<br>+ return joint_stream;<br> }<br> <br> /*!<br>- * XXX TODO The merge_capabilities() function needs to be split into<br>- * merging for new local topologies and new remote topologies. Also<br>- * the possibility of changing the stream types needs consideration.<br>- * Audio to video may or may not need us to keep the same RTP instance<br>- * because the stream position is still RTP. A new RTP instance would<br>- * cause us to change ports. Audio to image is definitely going to<br>- * happen for T.38.<br>+ * \internal<br>+ * \brief Determine if a merged topology should be rejected.<br>+ * \since 15.0.0<br> *<br>- * A new remote topology as an initial offer needs to dictate the<br>- * number of streams and the order. As a sdp_state option we may<br>- * allow creation of new active streams not defined by the current<br>- * local topology. A subsequent remote offer can change the stream<br>- * types and add streams. The sdp_state option could regulate<br>- * creation of new active streams here as well. An answer cannot<br>- * change stream types or the number of streams but can decline<br>- * streams. Any attempt to do so should report an error and possibly<br>- * disconnect the call.<br>+ * \param topology What topology to determine if we reject<br> *<br>- * A local topology update needs to be merged differently. It cannot<br>- * reduce the number of streams already defined without violating the<br>- * SDP RFC. The local merge could take the new topology stream<br>- * verbatim and add declined streams to fill out any shortfall with<br>- * the exiting topology. This strategy is needed if we want to change<br>- * an audio stream to an image stream for T.38 fax and vice versa.<br>- * The local merge could take the new topology and map the streams to<br>- * the existing local topology. The new topology stream format caps<br>- * would be copied into the merged topology so we could change what<br>- * codecs are negotiated.<br>+ * \retval 0 if not rejected.<br>+ * \retval non-zero if rejected.<br> */<br>+static int sdp_topology_is_rejected(struct ast_stream_topology *topology)<br>+{<br>+ int idx;<br>+ struct ast_stream *stream;<br>+<br>+ for (idx = ast_stream_topology_get_count(topology); idx--;) {<br>+ stream = ast_stream_topology_get_stream(topology, idx);<br>+ if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) {<br>+ /* At least one stream is not declined */<br>+ return 0;<br>+ }<br>+ }<br>+<br>+ /* All streams are declined */<br>+ return 1;<br>+}<br>+<br>+static void sdp_state_stream_copy_common(struct sdp_state_stream *dst, const struct sdp_state_stream *src)<br>+{<br>+ ast_sockaddr_copy(&dst->connection_address,<br>+ &src->connection_address);<br>+ /* Explicitly does not copy the local or remote hold states. */<br>+ dst->t38_local_params = src->t38_local_params;<br>+}<br>+<br>+static void sdp_state_stream_copy(struct sdp_state_stream *dst, const struct sdp_state_stream *src)<br>+{<br>+ *dst = *src;<br>+<br>+ switch (dst->type) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ ao2_bump(dst->rtp);<br>+ break;<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ ao2_bump(dst->udptl);<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ case AST_MEDIA_TYPE_END:<br>+ break;<br>+ }<br>+}<br>+<br> /*!<br>- * \brief Merge existing stream capabilities and a new topology into joint capabilities.<br>+ * \internal<br>+ * \brief Initialize an int vector and default the contents to the member index.<br>+ * \since 15.0.0<br>+ *<br>+ * \param vect Vetctor to initialize and set to default values.<br>+ * \param size Size of the vector to setup.<br>+ *<br>+ * \retval 0 on success.<br>+ * \retval -1 on failure.<br>+ */<br>+static int sdp_vect_idx_init(struct ast_vector_int *vect, size_t size)<br>+{<br>+ int idx;<br>+<br>+ if (AST_VECTOR_INIT(vect, size)) {<br>+ return -1;<br>+ }<br>+ for (idx = 0; idx < size; ++idx) {<br>+ AST_VECTOR_APPEND(vect, idx);<br>+ }<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Compare stream types for sort order.<br>+ * \since 15.0.0<br>+ *<br>+ * \param left Stream parameter on left<br>+ * \param right Stream parameter on right<br>+ *<br>+ * \retval <0 left stream sorts first.<br>+ * \retval =0 streams match.<br>+ * \retval >0 right stream sorts first.<br>+ */<br>+static int sdp_stream_cmp_by_type(const struct ast_stream *left, const struct ast_stream *right)<br>+{<br>+ enum ast_media_type left_type = ast_stream_get_type(left);<br>+ enum ast_media_type right_type = ast_stream_get_type(right);<br>+<br>+ /* Treat audio and image as the same for T.38 support */<br>+ if (left_type == AST_MEDIA_TYPE_IMAGE) {<br>+ left_type = AST_MEDIA_TYPE_AUDIO;<br>+ }<br>+ if (right_type == AST_MEDIA_TYPE_IMAGE) {<br>+ right_type = AST_MEDIA_TYPE_AUDIO;<br>+ }<br>+<br>+ return left_type - right_type;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Compare stream names and types for sort order.<br>+ * \since 15.0.0<br>+ *<br>+ * \param left Stream parameter on left<br>+ * \param right Stream parameter on right<br>+ *<br>+ * \retval <0 left stream sorts first.<br>+ * \retval =0 streams match.<br>+ * \retval >0 right stream sorts first.<br>+ */<br>+static int sdp_stream_cmp_by_name(const struct ast_stream *left, const struct ast_stream *right)<br>+{<br>+ int cmp;<br>+ const char *left_name;<br>+<br>+ left_name = ast_stream_get_name(left);<br>+ cmp = strcmp(left_name, ast_stream_get_name(right));<br>+ if (!cmp) {<br>+ cmp = sdp_stream_cmp_by_type(left, right);<br>+ if (!cmp) {<br>+ /* Are the stream names real or type names which aren't matchable? */<br>+ if (ast_strlen_zero(left_name)<br>+ || !strcmp(left_name, ast_codec_media_type2str(ast_stream_get_type(left)))<br>+ || !strcmp(left_name, ast_codec_media_type2str(ast_stream_get_type(right)))) {<br>+ /* The streams don't actually have real names */<br>+ cmp = -1;<br>+ }<br>+ }<br>+ }<br>+ return cmp;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Merge topology streams by the match function.<br>+ * \since 15.0.0<br>+ *<br>+ * \param sdp_state<br>+ * \param current_topology Topology to update with state.<br>+ * \param update_topology Topology to merge into the current topology.<br>+ * \param current_vect Stream index vector of remaining current_topology streams.<br>+ * \param update_vect Stream index vector of remaining update_topology streams.<br>+ * \param backfill_candidate Array of flags marking current_topology streams<br>+ * that can be reused for a different stream.<br>+ * \param match Stream comparison function to identify corresponding streams<br>+ * between the current_topology and update_topology.<br>+ * \param merged_topology Output topology of merged streams.<br>+ * \param compact_streams TRUE if backfill and limit number of streams.<br>+ *<br>+ * \retval 0 on success.<br>+ * \retval -1 on failure.<br>+ */<br>+static int sdp_merge_streams_match(<br>+ const struct ast_sdp_state *sdp_state,<br>+ const struct ast_stream_topology *current_topology,<br>+ const struct ast_stream_topology *update_topology,<br>+ struct ast_vector_int *current_vect,<br>+ struct ast_vector_int *update_vect,<br>+ char backfill_candidate[],<br>+ int (*match)(const struct ast_stream *left, const struct ast_stream *right),<br>+ struct ast_stream_topology *merged_topology,<br>+ int compact_streams)<br>+{<br>+ struct ast_stream *current_stream;<br>+ struct ast_stream *update_stream;<br>+ int current_idx;<br>+ int update_idx;<br>+ int idx;<br>+<br>+ for (current_idx = 0; current_idx < AST_VECTOR_SIZE(current_vect);) {<br>+ idx = AST_VECTOR_GET(current_vect, current_idx);<br>+ current_stream = ast_stream_topology_get_stream(current_topology, idx);<br>+<br>+ for (update_idx = 0; update_idx < AST_VECTOR_SIZE(update_vect); ++update_idx) {<br>+ idx = AST_VECTOR_GET(update_vect, update_idx);<br>+ update_stream = ast_stream_topology_get_stream(update_topology, idx);<br>+<br>+ if (match(current_stream, update_stream)) {<br>+ continue;<br>+ }<br>+<br>+ if (!compact_streams<br>+ || ast_stream_get_state(current_stream) != AST_STREAM_STATE_REMOVED<br>+ || ast_stream_get_state(update_stream) != AST_STREAM_STATE_REMOVED) {<br>+ struct ast_stream *merged_stream;<br>+<br>+ merged_stream = merge_local_stream(sdp_state->options, update_stream);<br>+ if (!merged_stream) {<br>+ return -1;<br>+ }<br>+ idx = AST_VECTOR_GET(current_vect, current_idx);<br>+ ast_stream_topology_set_stream(merged_topology, idx, merged_stream);<br>+<br>+ /*<br>+ * The current_stream cannot be considered a backfill_candidate<br>+ * anymore since it got updated.<br>+ *<br>+ * XXX It could be argued that if the declined status didn't<br>+ * change because the merged_stream became declined then we<br>+ * shouldn't remove the stream slot as a backfill_candidate<br>+ * and we shouldn't update the merged_topology stream. If we<br>+ * then backfilled the stream we would likely mess up the core<br>+ * if it is matching streams by type since the core attempted<br>+ * to update the stream with an incompatible stream. Any<br>+ * backfilled streams could cause a stream type ordering<br>+ * problem. However, we do need to reclaim declined stream<br>+ * slots sometime.<br>+ */<br>+ backfill_candidate[idx] = 0;<br>+ }<br>+<br>+ AST_VECTOR_REMOVE_ORDERED(current_vect, current_idx);<br>+ AST_VECTOR_REMOVE_ORDERED(update_vect, update_idx);<br>+ goto matched_next;<br>+ }<br>+<br>+ ++current_idx;<br>+matched_next:;<br>+ }<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Merge the current local topology with an updated topology.<br>+ * \since 15.0.0<br>+ *<br>+ * \param sdp_state<br>+ * \param current_topology Topology to update with state.<br>+ * \param update_topology Topology to merge into the current topology.<br>+ * \param compact_streams TRUE if backfill and limit number of streams.<br>+ *<br>+ * \retval merged topology on success.<br>+ * \retval NULL on failure.<br>+ */<br>+static struct ast_stream_topology *merge_local_topologies(<br>+ const struct ast_sdp_state *sdp_state,<br>+ const struct ast_stream_topology *current_topology,<br>+ const struct ast_stream_topology *update_topology,<br>+ int compact_streams)<br>+{<br>+ struct ast_stream_topology *merged_topology;<br>+ struct ast_stream *current_stream;<br>+ struct ast_stream *update_stream;<br>+ struct ast_stream *merged_stream;<br>+ struct ast_vector_int current_vect;<br>+ struct ast_vector_int update_vect;<br>+ int current_idx = ast_stream_topology_get_count(current_topology);<br>+ int update_idx;<br>+ int idx;<br>+ char backfill_candidate[current_idx];<br>+<br>+ memset(backfill_candidate, 0, current_idx);<br>+<br>+ if (compact_streams) {<br>+ /* Limit matching consideration to the maximum allowed live streams. */<br>+ idx = ast_sdp_options_get_max_streams(sdp_state->options);<br>+ if (idx < current_idx) {<br>+ current_idx = idx;<br>+ }<br>+ }<br>+ if (sdp_vect_idx_init(¤t_vect, current_idx)) {<br>+ return NULL;<br>+ }<br>+<br>+ if (sdp_vect_idx_init(&update_vect, ast_stream_topology_get_count(update_topology))) {<br>+ AST_VECTOR_FREE(¤t_vect);<br>+ return NULL;<br>+ }<br>+<br>+ merged_topology = ast_stream_topology_clone(current_topology);<br>+ if (!merged_topology) {<br>+ goto fail;<br>+ }<br>+<br>+ /*<br>+ * Remove any unsupported current streams from match consideration<br>+ * and mark potential backfill candidates.<br>+ */<br>+ for (current_idx = AST_VECTOR_SIZE(¤t_vect); current_idx--;) {<br>+ idx = AST_VECTOR_GET(¤t_vect, current_idx);<br>+ current_stream = ast_stream_topology_get_stream(current_topology, idx);<br>+ if (ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED<br>+ && compact_streams) {<br>+ /* The declined stream is a potential backfill candidate */<br>+ backfill_candidate[idx] = 1;<br>+ }<br>+ if (sdp_is_stream_type_supported(ast_stream_get_type(current_stream))) {<br>+ continue;<br>+ }<br>+ /* Unsupported current streams should always be declined */<br>+ ast_assert(ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED);<br>+<br>+ AST_VECTOR_REMOVE_ORDERED(¤t_vect, current_idx);<br>+ }<br>+<br>+ /* Remove any unsupported update streams from match consideration. */<br>+ for (update_idx = AST_VECTOR_SIZE(&update_vect); update_idx--;) {<br>+ idx = AST_VECTOR_GET(&update_vect, update_idx);<br>+ update_stream = ast_stream_topology_get_stream(update_topology, idx);<br>+ if (sdp_is_stream_type_supported(ast_stream_get_type(update_stream))) {<br>+ continue;<br>+ }<br>+<br>+ AST_VECTOR_REMOVE_ORDERED(&update_vect, update_idx);<br>+ }<br>+<br>+ /* Match by stream name and type */<br>+ if (sdp_merge_streams_match(sdp_state, current_topology, update_topology,<br>+ ¤t_vect, &update_vect, backfill_candidate, sdp_stream_cmp_by_name,<br>+ merged_topology, compact_streams)) {<br>+ goto fail;<br>+ }<br>+<br>+ /* Match by stream type */<br>+ if (sdp_merge_streams_match(sdp_state, current_topology, update_topology,<br>+ ¤t_vect, &update_vect, backfill_candidate, sdp_stream_cmp_by_type,<br>+ merged_topology, compact_streams)) {<br>+ goto fail;<br>+ }<br>+<br>+ /* Decline unmatched current stream slots */<br>+ for (current_idx = AST_VECTOR_SIZE(¤t_vect); current_idx--;) {<br>+ idx = AST_VECTOR_GET(¤t_vect, current_idx);<br>+ current_stream = ast_stream_topology_get_stream(current_topology, idx);<br>+<br>+ if (ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED) {<br>+ /* Stream is already declined. */<br>+ continue;<br>+ }<br>+<br>+ merged_stream = decline_stream(ast_stream_get_type(current_stream),<br>+ ast_stream_get_name(current_stream));<br>+ if (!merged_stream) {<br>+ goto fail;<br>+ }<br>+ ast_stream_topology_set_stream(merged_topology, idx, merged_stream);<br>+ }<br>+<br>+ /* Backfill new update stream slots into pre-existing declined current stream slots */<br>+ while (AST_VECTOR_SIZE(&update_vect)) {<br>+ idx = ast_stream_topology_get_count(current_topology);<br>+ for (current_idx = 0; current_idx < idx; ++current_idx) {<br>+ if (backfill_candidate[current_idx]) {<br>+ break;<br>+ }<br>+ }<br>+ if (idx <= current_idx) {<br>+ /* No more backfill candidates remain. */<br>+ break;<br>+ }<br>+ /* There should only be backfill stream slots when we are compact_streams */<br>+ ast_assert(compact_streams);<br>+<br>+ idx = AST_VECTOR_GET(&update_vect, 0);<br>+ update_stream = ast_stream_topology_get_stream(update_topology, idx);<br>+ AST_VECTOR_REMOVE_ORDERED(&update_vect, 0);<br>+<br>+ if (ast_stream_get_state(update_stream) == AST_STREAM_STATE_REMOVED) {<br>+ /* New stream is already declined so don't bother adding it. */<br>+ continue;<br>+ }<br>+<br>+ merged_stream = merge_local_stream(sdp_state->options, update_stream);<br>+ if (!merged_stream) {<br>+ goto fail;<br>+ }<br>+ if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) {<br>+ /* New stream not compatible so don't bother adding it. */<br>+ ast_stream_free(merged_stream);<br>+ continue;<br>+ }<br>+<br>+ /* Add the new stream into the backfill stream slot. */<br>+ ast_stream_topology_set_stream(merged_topology, current_idx, merged_stream);<br>+ backfill_candidate[current_idx] = 0;<br>+ }<br>+<br>+ /* Append any remaining new update stream slots that can fit. */<br>+ while (AST_VECTOR_SIZE(&update_vect)<br>+ && (!compact_streams<br>+ || ast_stream_topology_get_count(merged_topology)<br>+ < ast_sdp_options_get_max_streams(sdp_state->options))) {<br>+ idx = AST_VECTOR_GET(&update_vect, 0);<br>+ update_stream = ast_stream_topology_get_stream(update_topology, idx);<br>+ AST_VECTOR_REMOVE_ORDERED(&update_vect, 0);<br>+<br>+ if (ast_stream_get_state(update_stream) == AST_STREAM_STATE_REMOVED) {<br>+ /* New stream is already declined so don't bother adding it. */<br>+ continue;<br>+ }<br>+<br>+ merged_stream = merge_local_stream(sdp_state->options, update_stream);<br>+ if (!merged_stream) {<br>+ goto fail;<br>+ }<br>+ if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) {<br>+ /* New stream not compatible so don't bother adding it. */<br>+ ast_stream_free(merged_stream);<br>+ continue;<br>+ }<br>+<br>+ /* Append the new update stream. */<br>+ if (ast_stream_topology_append_stream(merged_topology, merged_stream) < 0) {<br>+ ast_stream_free(merged_stream);<br>+ goto fail;<br>+ }<br>+ }<br>+<br>+ AST_VECTOR_FREE(¤t_vect);<br>+ AST_VECTOR_FREE(&update_vect);<br>+ return merged_topology;<br>+<br>+fail:<br>+ ast_stream_topology_free(merged_topology);<br>+ AST_VECTOR_FREE(¤t_vect);<br>+ AST_VECTOR_FREE(&update_vect);<br>+ return NULL;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Remove declined streams appended beyond orig_topology.<br>+ * \since 15.0.0<br>+ *<br>+ * \param sdp_state<br>+ * \param orig_topology Negotiated or initial topology.<br>+ * \param new_topology New proposed topology.<br>+ *<br>+ * \return Nothing<br>+ */<br>+static void remove_appended_declined_streams(const struct ast_sdp_state *sdp_state,<br>+ const struct ast_stream_topology *orig_topology,<br>+ struct ast_stream_topology *new_topology)<br>+{<br>+ struct ast_stream *stream;<br>+ int orig_count;<br>+ int idx;<br>+<br>+ orig_count = ast_stream_topology_get_count(orig_topology);<br>+ for (idx = ast_stream_topology_get_count(new_topology); orig_count < idx;) {<br>+ --idx;<br>+ stream = ast_stream_topology_get_stream(new_topology, idx);<br>+ if (ast_stream_get_state(stream) != AST_STREAM_STATE_REMOVED) {<br>+ continue;<br>+ }<br>+ ast_stream_topology_del_stream(new_topology, idx);<br>+ }<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Setup a new state stream from a possibly existing state stream.<br>+ * \since 15.0.0<br>+ *<br>+ * \param sdp_state<br>+ * \param new_state_stream What state stream to setup<br>+ * \param old_state_stream Source of previous state stream information.<br>+ * May be NULL.<br>+ * \param new_type Type of the new state stream.<br>+ *<br>+ * \retval 0 on success.<br>+ * \retval -1 on failure.<br>+ */<br>+static int setup_new_stream_capabilities(<br>+ const struct ast_sdp_state *sdp_state,<br>+ struct sdp_state_stream *new_state_stream,<br>+ struct sdp_state_stream *old_state_stream,<br>+ enum ast_media_type new_type)<br>+{<br>+ if (old_state_stream) {<br>+ /*<br>+ * Copy everything potentially useful for a new stream state type<br>+ * from the old stream of a possible different type.<br>+ */<br>+ sdp_state_stream_copy_common(new_state_stream, old_state_stream);<br>+ /* We also need to preserve the locally_held state for the new stream. */<br>+ new_state_stream->locally_held = old_state_stream->locally_held;<br>+ }<br>+ new_state_stream->type = new_type;<br>+<br>+ switch (new_type) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ new_state_stream->rtp = create_rtp(sdp_state->options, new_type);<br>+ if (!new_state_stream->rtp) {<br>+ return -1;<br>+ }<br>+ break;<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ new_state_stream->udptl = create_udptl(sdp_state->options);<br>+ if (!new_state_stream->udptl) {<br>+ return -1;<br>+ }<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ case AST_MEDIA_TYPE_END:<br>+ break;<br>+ }<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \brief Merge existing stream capabilities and a new topology.<br> *<br> * \param sdp_state The state needing capabilities merged<br>- * \param new_topology The new topology to base merged capabilities on<br>- * \param is_local If new_topology is a local update.<br>+ * \param new_topology The topology to merge with our proposed capabilities<br> *<br> * \details<br>+ *<br> * This is a bit complicated. The idea is that we already have some<br> * capabilities set, and we've now been confronted with a new stream<br>- * topology. We want to take what's been presented to us and merge<br>- * those new capabilities with our own.<br>+ * topology from the system. We want to take what we had before and<br>+ * merge them with the new topology from the system.<br> *<br>- * For each of the new streams, we try to find a corresponding stream<br>- * in our proposed capabilities. If we find one, then we get the<br>- * compatible formats of the two streams and create a new stream with<br>- * those formats set. We then will re-use the underlying media<br>- * instance (such as an RTP instance) on this merged stream.<br>+ * According to the RFC, stream slots can change their types only if<br>+ * they are carrying the same logical information or an offer is<br>+ * reusing a declined slot or new stream slots are added to the end<br>+ * of the list. Switching a stream from audio to T.38 makes sense<br>+ * because the stream slot is carrying the same information just in a<br>+ * different format.<br> *<br>- * The is_local parameter determines whether we should attempt to<br>- * create new media instances. If we do not find a corresponding<br>- * stream, then we create a new one. If the is_local parameter is<br>- * true, this created stream is made a clone of the new stream, and a<br>- * media instance is created. If the is_local parameter is not true,<br>- * then the created stream has no formats set and no media instance is<br>- * created for it.<br>+ * We can setup new streams offered by the system up to our<br>+ * configured maximum stream slots. New stream slots requested over<br>+ * the maximum are discarded.<br> *<br> * \retval NULL An error occurred<br> * \retval non-NULL The merged capabilities<br> */<br>-static struct sdp_state_capabilities *merge_capabilities(const struct ast_sdp_state *sdp_state,<br>- const struct ast_stream_topology *new_topology, int is_local)<br>+static struct sdp_state_capabilities *merge_local_capabilities(<br>+ const struct ast_sdp_state *sdp_state,<br>+ const struct ast_stream_topology *new_topology)<br>+{<br>+ const struct sdp_state_capabilities *current = sdp_state->proposed_capabilities;<br>+ struct sdp_state_capabilities *merged_capabilities;<br>+ int idx;<br>+<br>+ ast_assert(current != NULL);<br>+<br>+ merged_capabilities = ast_calloc(1, sizeof(*merged_capabilities));<br>+ if (!merged_capabilities) {<br>+ return NULL;<br>+ }<br>+<br>+ merged_capabilities->topology = merge_local_topologies(sdp_state, current->topology,<br>+ new_topology, 1);<br>+ if (!merged_capabilities->topology) {<br>+ goto fail;<br>+ }<br>+ sdp_state_cb_offerer_modify_topology(sdp_state, merged_capabilities->topology);<br>+ remove_appended_declined_streams(sdp_state, current->topology,<br>+ merged_capabilities->topology);<br>+<br>+ if (AST_VECTOR_INIT(&merged_capabilities->streams,<br>+ ast_stream_topology_get_count(merged_capabilities->topology))) {<br>+ goto fail;<br>+ }<br>+<br>+ for (idx = 0; idx < ast_stream_topology_get_count(merged_capabilities->topology); ++idx) {<br>+ struct sdp_state_stream *merged_state_stream;<br>+ struct sdp_state_stream *current_state_stream;<br>+ struct ast_stream *merged_stream;<br>+ struct ast_stream *current_stream;<br>+ enum ast_media_type merged_stream_type;<br>+ enum ast_media_type current_stream_type;<br>+<br>+ merged_state_stream = ast_calloc(1, sizeof(*merged_state_stream));<br>+ if (!merged_state_stream) {<br>+ goto fail;<br>+ }<br>+<br>+ merged_stream = ast_stream_topology_get_stream(merged_capabilities->topology, idx);<br>+ merged_stream_type = ast_stream_get_type(merged_stream);<br>+<br>+ if (idx < ast_stream_topology_get_count(current->topology)) {<br>+ current_state_stream = AST_VECTOR_GET(¤t->streams, idx);<br>+ current_stream = ast_stream_topology_get_stream(current->topology, idx);<br>+ current_stream_type = ast_stream_get_type(current_stream);<br>+ } else {<br>+ /* The merged topology is adding a stream */<br>+ current_state_stream = NULL;<br>+ current_stream = NULL;<br>+ current_stream_type = AST_MEDIA_TYPE_UNKNOWN;<br>+ }<br>+<br>+ if (ast_stream_get_state(merged_stream) == AST_STREAM_STATE_REMOVED) {<br>+ if (current_state_stream) {<br>+ /* Copy everything potentially useful to a declined stream state. */<br>+ sdp_state_stream_copy_common(merged_state_stream, current_state_stream);<br>+ }<br>+ merged_state_stream->type = merged_stream_type;<br>+ } else if (!current_stream<br>+ || ast_stream_get_state(current_stream) == AST_STREAM_STATE_REMOVED) {<br>+ /* This is a new stream */<br>+ if (setup_new_stream_capabilities(sdp_state, merged_state_stream,<br>+ current_state_stream, merged_stream_type)) {<br>+ sdp_state_stream_free(merged_state_stream);<br>+ goto fail;<br>+ }<br>+ } else if (merged_stream_type == current_stream_type) {<br>+ /* Stream type is not changing. */<br>+ sdp_state_stream_copy(merged_state_stream, current_state_stream);<br>+ } else {<br>+ /*<br>+ * Stream type is changing. Need to replace the stream.<br>+ *<br>+ * Unsupported streams should already be handled earlier because<br>+ * they are always declined.<br>+ */<br>+ ast_assert(sdp_is_stream_type_supported(merged_stream_type));<br>+<br>+ /*<br>+ * XXX We might need to keep the old RTP instance if the new<br>+ * stream type is also RTP. We would just be changing between<br>+ * audio and video in that case. However we will create a new<br>+ * RTP instance anyway since its purpose has to be changing.<br>+ * Any RTP packets in flight from the old stream type might<br>+ * cause mischief.<br>+ */<br>+ if (setup_new_stream_capabilities(sdp_state, merged_state_stream,<br>+ current_state_stream, merged_stream_type)) {<br>+ sdp_state_stream_free(merged_state_stream);<br>+ goto fail;<br>+ }<br>+ }<br>+<br>+ if (AST_VECTOR_APPEND(&merged_capabilities->streams, merged_state_stream)) {<br>+ sdp_state_stream_free(merged_state_stream);<br>+ goto fail;<br>+ }<br>+ }<br>+<br>+ return merged_capabilities;<br>+<br>+fail:<br>+ sdp_state_capabilities_free(merged_capabilities);<br>+ return NULL;<br>+}<br>+<br>+static void merge_remote_stream_capabilities(<br>+ const struct ast_sdp_state *sdp_state,<br>+ struct sdp_state_stream *joint_state_stream,<br>+ struct sdp_state_stream *local_state_stream,<br>+ struct ast_stream *remote_stream)<br>+{<br>+ struct ast_rtp_codecs *codecs;<br>+<br>+ *joint_state_stream = *local_state_stream;<br>+ /*<br>+ * Need to explicitly set the type to the remote because we could<br>+ * be changing the type between audio and video.<br>+ */<br>+ joint_state_stream->type = ast_stream_get_type(remote_stream);<br>+<br>+ switch (joint_state_stream->type) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ ao2_bump(joint_state_stream->rtp);<br>+ codecs = ast_stream_get_data(remote_stream, AST_STREAM_DATA_RTP_CODECS);<br>+ ast_assert(codecs != NULL);<br>+ if (sdp_state->role == SDP_ROLE_ANSWERER) {<br>+ /*<br>+ * Setup rx payload type mapping to prefer the mapping<br>+ * from the peer that the RFC says we SHOULD use.<br>+ */<br>+ ast_rtp_codecs_payloads_xover(codecs, codecs, NULL);<br>+ }<br>+ ast_rtp_codecs_payloads_copy(codecs,<br>+ ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance),<br>+ joint_state_stream->rtp->instance);<br>+ break;<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ joint_state_stream->udptl = ao2_bump(joint_state_stream->udptl);<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ case AST_MEDIA_TYPE_END:<br>+ break;<br>+ }<br>+}<br>+<br>+static int create_remote_stream_capabilities(<br>+ const struct ast_sdp_state *sdp_state,<br>+ struct sdp_state_stream *joint_state_stream,<br>+ struct sdp_state_stream *local_state_stream,<br>+ struct ast_stream *remote_stream)<br>+{<br>+ struct ast_rtp_codecs *codecs;<br>+<br>+ /* We can only create streams if we are the answerer */<br>+ ast_assert(sdp_state->role == SDP_ROLE_ANSWERER);<br>+<br>+ if (local_state_stream) {<br>+ /*<br>+ * Copy everything potentially useful for a new stream state type<br>+ * from the old stream of a possible different type.<br>+ */<br>+ sdp_state_stream_copy_common(joint_state_stream, local_state_stream);<br>+ /* We also need to preserve the locally_held state for the new stream. */<br>+ joint_state_stream->locally_held = local_state_stream->locally_held;<br>+ }<br>+ joint_state_stream->type = ast_stream_get_type(remote_stream);<br>+<br>+ switch (joint_state_stream->type) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ joint_state_stream->rtp = create_rtp(sdp_state->options, joint_state_stream->type);<br>+ if (!joint_state_stream->rtp) {<br>+ return -1;<br>+ }<br>+<br>+ /*<br>+ * Setup rx payload type mapping to prefer the mapping<br>+ * from the peer that the RFC says we SHOULD use.<br>+ */<br>+ codecs = ast_stream_get_data(remote_stream, AST_STREAM_DATA_RTP_CODECS);<br>+ ast_assert(codecs != NULL);<br>+ ast_rtp_codecs_payloads_xover(codecs, codecs, NULL);<br>+ ast_rtp_codecs_payloads_copy(codecs,<br>+ ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance),<br>+ joint_state_stream->rtp->instance);<br>+ break;<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ joint_state_stream->udptl = create_udptl(sdp_state->options);<br>+ if (!joint_state_stream->udptl) {<br>+ return -1;<br>+ }<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ case AST_MEDIA_TYPE_END:<br>+ break;<br>+ }<br>+ return 0;<br>+}<br>+<br>+/*!<br>+ * \internal<br>+ * \brief Create a joint topology from the remote topology.<br>+ * \since 15.0.0<br>+ *<br>+ * \param sdp_state The state needing capabilities merged.<br>+ * \param local Capabilities to merge the remote topology into.<br>+ * \param remote_topology The topology to merge with our local capabilities.<br>+ *<br>+ * \retval joint topology on success.<br>+ * \retval NULL on failure.<br>+ */<br>+static struct ast_stream_topology *merge_remote_topology(<br>+ const struct ast_sdp_state *sdp_state,<br>+ const struct sdp_state_capabilities *local,<br>+ const struct ast_stream_topology *remote_topology)<br>+{<br>+ struct ast_stream_topology *joint_topology;<br>+ int idx;<br>+<br>+ joint_topology = ast_stream_topology_alloc();<br>+ if (!joint_topology) {<br>+ return NULL;<br>+ }<br>+<br>+ for (idx = 0; idx < ast_stream_topology_get_count(remote_topology); ++idx) {<br>+ enum ast_media_type local_stream_type;<br>+ enum ast_media_type remote_stream_type;<br>+ struct ast_stream *remote_stream;<br>+ struct ast_stream *local_stream;<br>+ struct ast_stream *joint_stream;<br>+ struct sdp_state_stream *local_state_stream;<br>+<br>+ remote_stream = ast_stream_topology_get_stream(remote_topology, idx);<br>+ remote_stream_type = ast_stream_get_type(remote_stream);<br>+<br>+ if (idx < ast_stream_topology_get_count(local->topology)) {<br>+ local_state_stream = AST_VECTOR_GET(&local->streams, idx);<br>+ local_stream = ast_stream_topology_get_stream(local->topology, idx);<br>+ local_stream_type = ast_stream_get_type(local_stream);<br>+ } else {<br>+ /* The remote is adding a stream slot */<br>+ local_state_stream = NULL;<br>+ local_stream = NULL;<br>+ local_stream_type = AST_MEDIA_TYPE_UNKNOWN;<br>+<br>+ if (sdp_state->role != SDP_ROLE_ANSWERER) {<br>+ /* Remote cannot add a new stream slot in an answer SDP */<br>+ ast_debug(1,<br>+ "Bad. Ignoring new %s stream slot remote answer SDP trying to add.\n",<br>+ ast_codec_media_type2str(remote_stream_type));<br>+ continue;<br>+ }<br>+ }<br>+<br>+ if (local_stream<br>+ && ast_stream_get_state(local_stream) != AST_STREAM_STATE_REMOVED) {<br>+ if (remote_stream_type == local_stream_type) {<br>+ /* Stream type is not changing. */<br>+ joint_stream = merge_remote_stream(sdp_state, local_stream,<br>+ local_state_stream->locally_held, remote_stream);<br>+ } else if (sdp_state->role == SDP_ROLE_ANSWERER) {<br>+ /* Stream type is changing. */<br>+ joint_stream = merge_remote_stream(sdp_state, NULL,<br>+ local_state_stream->locally_held, remote_stream);<br>+ } else {<br>+ /*<br>+ * Remote cannot change the stream type we offered.<br>+ * Mark as declined.<br>+ */<br>+ ast_debug(1,<br>+ "Bad. Remote answer SDP trying to change the stream type from %s to %s.\n",<br>+ ast_codec_media_type2str(local_stream_type),<br>+ ast_codec_media_type2str(remote_stream_type));<br>+ joint_stream = decline_stream(local_stream_type,<br>+ ast_stream_get_name(local_stream));<br>+ }<br>+ } else {<br>+ /* Local stream is either dead/declined or nonexistent. */<br>+ if (sdp_state->role == SDP_ROLE_ANSWERER) {<br>+ if (sdp_is_stream_type_supported(remote_stream_type)<br>+ && ast_stream_get_state(remote_stream) != AST_STREAM_STATE_REMOVED<br>+ && idx < ast_sdp_options_get_max_streams(sdp_state->options)) {<br>+ /* Try to create the new stream */<br>+ joint_stream = merge_remote_stream(sdp_state, NULL,<br>+ local_state_stream ? local_state_stream->locally_held : 0,<br>+ remote_stream);<br>+ } else {<br>+ const char *stream_name;<br>+<br>+ /* Decline the remote stream. */<br>+ if (local_stream<br>+ && local_stream_type == remote_stream_type) {<br>+ /* Preserve the previous stream name */<br>+ stream_name = ast_stream_get_name(local_stream);<br>+ } else {<br>+ stream_name = NULL;<br>+ }<br>+ joint_stream = decline_stream(remote_stream_type, stream_name);<br>+ }<br>+ } else {<br>+ /* Decline the stream. */<br>+ if (DEBUG_ATLEAST(1)<br>+ && ast_stream_get_state(remote_stream) != AST_STREAM_STATE_REMOVED) {<br>+ /*<br>+ * Remote cannot request a new stream in place of a declined<br>+ * stream in an answer SDP.<br>+ */<br>+ ast_log(LOG_DEBUG,<br>+ "Bad. Remote answer SDP trying to use a declined stream slot for %s.\n",<br>+ ast_codec_media_type2str(remote_stream_type));<br>+ }<br>+ joint_stream = decline_stream(local_stream_type,<br>+ ast_stream_get_name(local_stream));<br>+ }<br>+ }<br>+<br>+ if (!joint_stream) {<br>+ goto fail;<br>+ }<br>+ if (ast_stream_topology_append_stream(joint_topology, joint_stream) < 0) {<br>+ ast_stream_free(joint_stream);<br>+ goto fail;<br>+ }<br>+ }<br>+<br>+ return joint_topology;<br>+<br>+fail:<br>+ ast_stream_topology_free(joint_topology);<br>+ return NULL;<br>+}<br>+<br>+/*!<br>+ * \brief Merge our stream capabilities and a remote topology into joint capabilities.<br>+ *<br>+ * \param sdp_state The state needing capabilities merged<br>+ * \param remote_topology The topology to merge with our proposed capabilities<br>+ *<br>+ * \details<br>+ * This is a bit complicated. The idea is that we already have some<br>+ * capabilities set, and we've now been confronted with a stream<br>+ * topology from the remote end. We want to take what's been<br>+ * presented to us and merge those new capabilities with our own.<br>+ *<br>+ * According to the RFC, stream slots can change their types only if<br>+ * they are carrying the same logical information or an offer is<br>+ * reusing a declined slot or new stream slots are added to the end<br>+ * of the list. Switching a stream from audio to T.38 makes sense<br>+ * because the stream slot is carrying the same information just in a<br>+ * different format.<br>+ *<br>+ * When we are the answerer we can setup new streams offered by the<br>+ * remote up to our configured maximum stream slots. New stream<br>+ * slots offered over the maximum are unconditionally declined.<br>+ *<br>+ * \retval NULL An error occurred<br>+ * \retval non-NULL The merged capabilities<br>+ */<br>+static struct sdp_state_capabilities *merge_remote_capabilities(<br>+ const struct ast_sdp_state *sdp_state,<br>+ const struct ast_stream_topology *remote_topology)<br> {<br> const struct sdp_state_capabilities *local = sdp_state->proposed_capabilities;<br> struct sdp_state_capabilities *joint_capabilities;<br>- int media_indices[AST_MEDIA_TYPE_END] = {0};<br>- int i;<br>- static const char dummy_name[] = "dummy";<br>+ int idx;<br> <br> ast_assert(local != NULL);<br> <br>@@ -700,150 +1975,131 @@<br> return NULL;<br> }<br> <br>- joint_capabilities->topology = ast_stream_topology_alloc();<br>+ joint_capabilities->topology = merge_remote_topology(sdp_state, local, remote_topology);<br> if (!joint_capabilities->topology) {<br> goto fail;<br> }<br> <br>- if (AST_VECTOR_INIT(&joint_capabilities->streams, AST_VECTOR_SIZE(&local->streams))) {<br>+ if (sdp_state->role == SDP_ROLE_ANSWERER) {<br>+ sdp_state_cb_answerer_modify_topology(sdp_state, joint_capabilities->topology);<br>+ }<br>+ idx = ast_stream_topology_get_count(joint_capabilities->topology);<br>+ if (AST_VECTOR_INIT(&joint_capabilities->streams, idx)) {<br> goto fail;<br> }<br>- ast_sockaddr_copy(&joint_capabilities->connection_address, &local->connection_address);<br> <br>- for (i = 0; i < ast_stream_topology_get_count(new_topology); ++i) {<br>- enum ast_media_type new_stream_type;<br>- struct ast_stream *new_stream;<br>+ for (idx = 0; idx < ast_stream_topology_get_count(remote_topology); ++idx) {<br>+ enum ast_media_type local_stream_type;<br>+ enum ast_media_type remote_stream_type;<br>+ struct ast_stream *remote_stream;<br> struct ast_stream *local_stream;<br> struct ast_stream *joint_stream;<br>+ struct sdp_state_stream *local_state_stream;<br> struct sdp_state_stream *joint_state_stream;<br>- int local_index;<br> <br> joint_state_stream = ast_calloc(1, sizeof(*joint_state_stream));<br> if (!joint_state_stream) {<br> goto fail;<br> }<br> <br>- new_stream = ast_stream_topology_get_stream(new_topology, i);<br>- new_stream_type = ast_stream_get_type(new_stream);<br>+ remote_stream = ast_stream_topology_get_stream(remote_topology, idx);<br>+ remote_stream_type = ast_stream_get_type(remote_stream);<br> <br>- local_index = get_corresponding_index(local->topology, new_stream_type, media_indices);<br>- if (0 <= local_index) {<br>- local_stream = ast_stream_topology_get_stream(local->topology, local_index);<br>- if (!strcmp(ast_stream_get_name(local_stream), dummy_name)) {<br>- /* The local stream is a non-exixtent dummy stream. */<br>- local_stream = NULL;<br>- }<br>+ if (idx < ast_stream_topology_get_count(local->topology)) {<br>+ local_state_stream = AST_VECTOR_GET(&local->streams, idx);<br>+ local_stream = ast_stream_topology_get_stream(local->topology, idx);<br>+ local_stream_type = ast_stream_get_type(local_stream);<br> } else {<br>+ /* The remote is adding a stream slot */<br>+ local_state_stream = NULL;<br> local_stream = NULL;<br>+ local_stream_type = AST_MEDIA_TYPE_UNKNOWN;<br>+<br>+ if (sdp_state->role != SDP_ROLE_ANSWERER) {<br>+ /* Remote cannot add a new stream slot in an answer SDP */<br>+ sdp_state_stream_free(joint_state_stream);<br>+ break;<br>+ }<br> }<br>- if (local_stream) {<br>- struct sdp_state_stream *local_state_stream;<br>- struct ast_rtp_codecs *codecs;<br> <br>- if (is_local) {<br>- /* Replace the local stream with the new local stream. */<br>- joint_stream = ast_stream_clone(new_stream, NULL);<br>+ joint_stream = ast_stream_topology_get_stream(joint_capabilities->topology,<br>+ idx);<br>+<br>+ if (local_stream<br>+ && ast_stream_get_state(local_stream) != AST_STREAM_STATE_REMOVED) {<br>+ if (ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED) {<br>+ /* Copy everything potentially useful to a declined stream state. */<br>+ sdp_state_stream_copy_common(joint_state_stream, local_state_stream);<br>+<br>+ joint_state_stream->type = ast_stream_get_type(joint_stream);<br>+ } else if (remote_stream_type == local_stream_type) {<br>+ /* Stream type is not changing. */<br>+ merge_remote_stream_capabilities(sdp_state, joint_state_stream,<br>+ local_state_stream, remote_stream);<br>+ ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream));<br> } else {<br>- joint_stream = merge_streams(local_stream, new_stream);<br>- }<br>- if (!joint_stream) {<br>- sdp_state_stream_free(joint_state_stream);<br>- goto fail;<br>- }<br>-<br>- local_state_stream = AST_VECTOR_GET(&local->streams, local_index);<br>- joint_state_stream->type = local_state_stream->type;<br>-<br>- switch (joint_state_stream->type) {<br>- case AST_MEDIA_TYPE_AUDIO:<br>- case AST_MEDIA_TYPE_VIDEO:<br>- joint_state_stream->rtp = ao2_bump(local_state_stream->rtp);<br>- if (is_local) {<br>- break;<br>- }<br>- codecs = ast_stream_get_data(new_stream, AST_STREAM_DATA_RTP_CODECS);<br>- ast_assert(codecs != NULL);<br>- if (sdp_state->role == SDP_ROLE_ANSWERER) {<br>- /*<br>- * Setup rx payload type mapping to prefer the mapping<br>- * from the peer that the RFC says we SHOULD use.<br>- */<br>- ast_rtp_codecs_payloads_xover(codecs, codecs, NULL);<br>- }<br>- ast_rtp_codecs_payloads_copy(codecs,<br>- ast_rtp_instance_get_codecs(joint_state_stream->rtp->instance),<br>- joint_state_stream->rtp->instance);<br>- break;<br>- case AST_MEDIA_TYPE_IMAGE:<br>- joint_state_stream->udptl = ao2_bump(local_state_stream->udptl);<br>- joint_state_stream->t38_local_params = local_state_stream->t38_local_params;<br>- break;<br>- case AST_MEDIA_TYPE_UNKNOWN:<br>- case AST_MEDIA_TYPE_TEXT:<br>- case AST_MEDIA_TYPE_END:<br>- break;<br>- }<br>-<br>- if (!ast_sockaddr_isnull(&local_state_stream->connection_address)) {<br>- ast_sockaddr_copy(&joint_state_stream->connection_address,<br>- &local_state_stream->connection_address);<br>- } else {<br>- ast_sockaddr_setnull(&joint_state_stream->connection_address);<br>- }<br>- joint_state_stream->locally_held = local_state_stream->locally_held;<br>- } else if (is_local) {<br>- /* We don't have a stream state that corresponds to the stream in the new topology, so<br>- * create a stream state as appropriate.<br>- */<br>- joint_stream = ast_stream_clone(new_stream, NULL);<br>- if (!joint_stream) {<br>- sdp_state_stream_free(joint_state_stream);<br>- goto fail;<br>- }<br>-<br>- switch (new_stream_type) {<br>- case AST_MEDIA_TYPE_AUDIO:<br>- case AST_MEDIA_TYPE_VIDEO:<br>- joint_state_stream->rtp = create_rtp(sdp_state->options,<br>- new_stream_type);<br>- if (!joint_state_stream->rtp) {<br>- ast_stream_free(joint_stream);<br>+ /*<br>+ * Stream type is changing. Need to replace the stream.<br>+ *<br>+ * XXX We might need to keep the old RTP instance if the new<br>+ * stream type is also RTP. We would just be changing between<br>+ * audio and video in that case. However we will create a new<br>+ * RTP instance anyway since its purpose has to be changing.<br>+ * Any RTP packets in flight from the old stream type might<br>+ * cause mischief.<br>+ */<br>+ if (create_remote_stream_capabilities(sdp_state, joint_state_stream,<br>+ local_state_stream, remote_stream)) {<br> sdp_state_stream_free(joint_state_stream);<br> goto fail;<br> }<br>- break;<br>- case AST_MEDIA_TYPE_IMAGE:<br>- joint_state_stream->udptl = create_udptl(sdp_state->options);<br>- if (!joint_state_stream->udptl) {<br>- ast_stream_free(joint_stream);<br>- sdp_state_stream_free(joint_state_stream);<br>- goto fail;<br>- }<br>- break;<br>- case AST_MEDIA_TYPE_UNKNOWN:<br>- case AST_MEDIA_TYPE_TEXT:<br>- case AST_MEDIA_TYPE_END:<br>- break;<br>+ ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream));<br> }<br>- ast_sockaddr_setnull(&joint_state_stream->connection_address);<br>- joint_state_stream->locally_held = 0;<br> } else {<br>- /* We don't have a stream that corresponds to the stream in the new topology. Create a<br>- * dummy stream to go in its place so that the resulting SDP created will contain<br>- * the stream but will have no port or codecs set<br>- */<br>- joint_stream = ast_stream_alloc(dummy_name, new_stream_type);<br>- if (!joint_stream) {<br>- sdp_state_stream_free(joint_state_stream);<br>- goto fail;<br>+ /* Local stream is either dead/declined or nonexistent. */<br>+ if (sdp_state->role == SDP_ROLE_ANSWERER) {<br>+ if (ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED) {<br>+ if (local_state_stream) {<br>+ /* Copy everything potentially useful to a declined stream state. */<br>+ sdp_state_stream_copy_common(joint_state_stream, local_state_stream);<br>+ }<br>+ joint_state_stream->type = ast_stream_get_type(joint_stream);<br>+ } else {<br>+ /* Try to create the new stream */<br>+ if (create_remote_stream_capabilities(sdp_state, joint_state_stream,<br>+ local_state_stream, remote_stream)) {<br>+ sdp_state_stream_free(joint_state_stream);<br>+ goto fail;<br>+ }<br>+ ast_assert(joint_state_stream->type == ast_stream_get_type(joint_stream));<br>+ }<br>+ } else {<br>+ /* Decline the stream. */<br>+ ast_assert(ast_stream_get_state(joint_stream) == AST_STREAM_STATE_REMOVED);<br>+ if (local_state_stream) {<br>+ /* Copy everything potentially useful to a declined stream state. */<br>+ sdp_state_stream_copy_common(joint_state_stream, local_state_stream);<br>+ }<br>+ joint_state_stream->type = ast_stream_get_type(joint_stream);<br> }<br> }<br> <br>- if (ast_stream_topology_append_stream(joint_capabilities->topology, joint_stream) < 0) {<br>- ast_stream_free(joint_stream);<br>- sdp_state_stream_free(joint_state_stream);<br>- goto fail;<br>+ /* Determine if the remote placed the stream on hold. */<br>+ joint_state_stream->remotely_held = 0;<br>+ if (ast_stream_get_state(joint_stream) != AST_STREAM_STATE_REMOVED) {<br>+ enum ast_stream_state remote_state;<br>+<br>+ remote_state = ast_stream_get_state(remote_stream);<br>+ switch (remote_state) {<br>+ case AST_STREAM_STATE_INACTIVE:<br>+ case AST_STREAM_STATE_SENDONLY:<br>+ joint_state_stream->remotely_held = 1;<br>+ break;<br>+ default:<br>+ break;<br>+ }<br> }<br>+<br> if (AST_VECTOR_APPEND(&joint_capabilities->streams, joint_state_stream)) {<br> sdp_state_stream_free(joint_state_stream);<br> goto fail;<br>@@ -998,11 +2254,6 @@<br> struct ast_sdp_c_line *c_line;<br> struct ast_sockaddr *addrs;<br> <br>- if (!rtp) {<br>- /* This is a dummy stream */<br>- return;<br>- }<br>-<br> c_line = remote_m_line->c_line;<br> if (!c_line) {<br> c_line = remote_sdp->c_line;<br>@@ -1058,11 +2309,6 @@<br> unsigned int fax_max_datagram;<br> struct ast_sockaddr *addrs;<br> <br>- if (!udptl) {<br>- /* This is a dummy stream */<br>- return;<br>- }<br>-<br> a_line = ast_sdp_m_find_attribute(remote_m_line, "t38faxmaxdatagram", -1);<br> if (!a_line) {<br> a_line = ast_sdp_m_find_attribute(remote_m_line, "t38maxdatagram", -1);<br>@@ -1102,6 +2348,49 @@<br> }<br> }<br> <br>+static void sdp_apply_negotiated_state(struct ast_sdp_state *sdp_state)<br>+{<br>+ struct sdp_state_capabilities *capabilities = sdp_state->negotiated_capabilities;<br>+ int idx;<br>+<br>+ if (!capabilities) {<br>+ /* Nothing to apply */<br>+ return;<br>+ }<br>+<br>+ sdp_state_cb_preapply_topology(sdp_state, capabilities->topology);<br>+ for (idx = 0; idx < AST_VECTOR_SIZE(&capabilities->streams); ++idx) {<br>+ struct sdp_state_stream *state_stream;<br>+ struct ast_stream *stream;<br>+<br>+ stream = ast_stream_topology_get_stream(capabilities->topology, idx);<br>+ if (ast_stream_get_state(stream) == AST_STREAM_STATE_REMOVED) {<br>+ /* Stream is declined */<br>+ continue;<br>+ }<br>+<br>+ state_stream = AST_VECTOR_GET(&capabilities->streams, idx);<br>+ switch (ast_stream_get_type(stream)) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ update_rtp_after_merge(sdp_state, state_stream->rtp, sdp_state->options,<br>+ sdp_state->remote_sdp, ast_sdp_get_m(sdp_state->remote_sdp, idx));<br>+ break;<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ update_udptl_after_merge(sdp_state, state_stream->udptl, sdp_state->options,<br>+ sdp_state->remote_sdp, ast_sdp_get_m(sdp_state->remote_sdp, idx));<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ case AST_MEDIA_TYPE_END:<br>+ /* All unsupported streams are declined */<br>+ ast_assert(0);<br>+ break;<br>+ }<br>+ }<br>+ sdp_state_cb_postapply_topology(sdp_state, capabilities->topology);<br>+}<br>+<br> static void set_negotiated_capabilities(struct ast_sdp_state *sdp_state,<br> struct sdp_state_capabilities *new_capabilities)<br> {<br>@@ -1120,6 +2409,81 @@<br> sdp_state_capabilities_free(old_capabilities);<br> }<br> <br>+/*!<br>+ * \internal<br>+ * \brief Copy the new capabilities into the proposed capabilities.<br>+ * \since 15.0.0<br>+ *<br>+ * \param sdp_state The current SDP state<br>+ * \param new_capabilities Capabilities to copy<br>+ *<br>+ * \retval 0 on success.<br>+ * \retval -1 on failure.<br>+ */<br>+static int update_proposed_capabilities(struct ast_sdp_state *sdp_state,<br>+ struct sdp_state_capabilities *new_capabilities)<br>+{<br>+ struct sdp_state_capabilities *proposed_capabilities;<br>+ int idx;<br>+<br>+ proposed_capabilities = ast_calloc(1, sizeof(*proposed_capabilities));<br>+ if (!proposed_capabilities) {<br>+ return -1;<br>+ }<br>+<br>+ proposed_capabilities->topology = ast_stream_topology_clone(new_capabilities->topology);<br>+ if (!proposed_capabilities->topology) {<br>+ goto fail;<br>+ }<br>+<br>+ if (AST_VECTOR_INIT(&proposed_capabilities->streams,<br>+ AST_VECTOR_SIZE(&new_capabilities->streams))) {<br>+ goto fail;<br>+ }<br>+<br>+ for (idx = 0; idx < AST_VECTOR_SIZE(&new_capabilities->streams); ++idx) {<br>+ struct sdp_state_stream *proposed_state_stream;<br>+ struct sdp_state_stream *new_state_stream;<br>+<br>+ proposed_state_stream = ast_calloc(1, sizeof(*proposed_state_stream));<br>+ if (!proposed_state_stream) {<br>+ goto fail;<br>+ }<br>+<br>+ new_state_stream = AST_VECTOR_GET(&new_capabilities->streams, idx);<br>+ *proposed_state_stream = *new_state_stream;<br>+<br>+ switch (proposed_state_stream->type) {<br>+ case AST_MEDIA_TYPE_AUDIO:<br>+ case AST_MEDIA_TYPE_VIDEO:<br>+ ao2_bump(proposed_state_stream->rtp);<br>+ break;<br>+ case AST_MEDIA_TYPE_IMAGE:<br>+ ao2_bump(proposed_state_stream->udptl);<br>+ break;<br>+ case AST_MEDIA_TYPE_UNKNOWN:<br>+ case AST_MEDIA_TYPE_TEXT:<br>+ case AST_MEDIA_TYPE_END:<br>+ break;<br>+ }<br>+<br>+ /* This is explicitly never set on the proposed capabilities struct */<br>+ proposed_state_stream->remotely_held = 0;<br>+<br>+ if (AST_VECTOR_APPEND(&proposed_capabilities->streams, proposed_state_stream)) {<br>+ sdp_state_stream_free(proposed_state_stream);<br>+ goto fail;<br>+ }<br>+ }<br>+<br>+ set_proposed_capabilities(sdp_state, proposed_capabilities);<br>+ return 0;<br>+<br>+fail:<br>+ sdp_state_capabilities_free(proposed_capabilities);<br>+ return -1;<br>+}<br>+<br> static struct ast_sdp *sdp_create_from_state(const struct ast_sdp_state *sdp_state,<br> const struct sdp_state_capabilities *capabilities);<br> <br>@@ -1127,65 +2491,47 @@<br> * \brief Merge SDPs into a joint SDP.<br> *<br> * This function is used to take a remote SDP and merge it with our local<br>- * capabilities to produce a new local SDP. After creating the new local SDP,<br>- * it then iterates through media instances and updates them as necessary. For<br>+ * capabilities to produce a new local SDP. After creating the new local SDP,<br>+ * it then iterates through media instances and updates them as necessary. For<br> * instance, if a specific RTP feature is supported by both us and the far end,<br> * then we can ensure that the feature is enabled.<br> *<br> * \param sdp_state The current SDP state<br>- * \retval -1 Failure<br>+ *<br> * \retval 0 Success<br>+ * \retval -1 Failure<br>+ * Use ast_sdp_state_is_offer_rejected() to see if the offer SDP was rejected.<br> */<br> static int merge_sdps(struct ast_sdp_state *sdp_state, const struct ast_sdp *remote_sdp)<br> {<br> struct sdp_state_capabilities *joint_capabilities;<br> struct ast_stream_topology *remote_capabilities;<br>- int i;<br> <br> remote_capabilities = ast_get_topology_from_sdp(remote_sdp,<br>- sdp_state->options->g726_non_standard);<br>+ ast_sdp_options_get_g726_non_standard(sdp_state->options));<br> if (!remote_capabilities) {<br> return -1;<br> }<br> <br>- joint_capabilities = merge_capabilities(sdp_state, remote_capabilities, 0);<br>+ joint_capabilities = merge_remote_capabilities(sdp_state, remote_capabilities);<br> ast_stream_topology_free(remote_capabilities);<br> if (!joint_capabilities) {<br> return -1;<br> }<br>- set_negotiated_capabilities(sdp_state, joint_capabilities);<br>-<br>- if (sdp_state->local_sdp) {<br>- ast_sdp_free(sdp_state->local_sdp);<br>- sdp_state->local_sdp = NULL;<br>- }<br>-<br>- sdp_state->local_sdp = sdp_create_from_state(sdp_state, joint_capabilities);<br>- if (!sdp_state->local_sdp) {<br>- return -1;<br>- }<br>-<br>- for (i = 0; i < AST_VECTOR_SIZE(&joint_capabilities->streams); ++i) {<br>- struct sdp_state_stream *state_stream;<br>-<br>- state_stream = AST_VECTOR_GET(&joint_capabilities->streams, i);<br>-<br>- switch (ast_stream_get_type(ast_stream_topology_get_stream(joint_capabilities->topology, i))) {<br>- case AST_MEDIA_TYPE_AUDIO:<br>- case AST_MEDIA_TYPE_VIDEO:<br>- update_rtp_after_merge(sdp_state, state_stream->rtp, sdp_state->options,<br>- remote_sdp, ast_sdp_get_m(remote_sdp, i));<br>- break;<br>- case AST_MEDIA_TYPE_IMAGE:<br>- update_udptl_after_merge(sdp_state, state_stream->udptl, sdp_state->options,<br>- remote_sdp, ast_sdp_get_m(remote_sdp, i));<br>- break;<br>- case AST_MEDIA_TYPE_UNKNOWN:<br>- case AST_MEDIA_TYPE_TEXT:<br>- case AST_MEDIA_TYPE_END:<br>- break;<br>+ if (sdp_state->role == SDP_ROLE_ANSWERER) {<br>+ sdp_state->remote_offer_rejected =<br>+ sdp_topology_is_rejected(joint_capabilities->topology) ? 1 : 0;<br>+ if (sdp_state->remote_offer_rejected) {<br>+ sdp_state_capabilities_free(joint_capabilities);<br>+ return -1;<br> }<br> }<br>+ set_negotiated_capabilities(sdp_state, joint_capabilities);<br>+<br>+ ao2_cleanup(sdp_state->remote_sdp);<br>+ sdp_state->remote_sdp = ao2_bump((struct ast_sdp *) remote_sdp);<br>+<br>+ sdp_apply_negotiated_state(sdp_state);<br> <br> return 0;<br> }<br>@@ -1194,10 +2540,43 @@<br> {<br> ast_assert(sdp_state != NULL);<br> <br>- if (sdp_state->role == SDP_ROLE_NOT_SET) {<br>+ switch (sdp_state->role) {<br>+ case SDP_ROLE_NOT_SET:<br> ast_assert(sdp_state->local_sdp == NULL);<br> sdp_state->role = SDP_ROLE_OFFERER;<br>+<br>+ if (sdp_state->pending_topology_update) {<br>+ struct sdp_state_capabilities *capabilities;<br>+<br>+ /* We have a topology update to perform before generating the offer */<br>+ capabilities = merge_local_capabilities(sdp_state,<br>+ sdp_state->pending_topology_update);<br>+ if (!capabilities) {<br>+ break;<br>+ }<br>+ ast_stream_topology_free(sdp_state->pending_topology_update);<br>+ sdp_state->pending_topology_update = NULL;<br>+ set_proposed_capabilities(sdp_state, capabilities);<br>+ }<br>+<br>+ /*<br>+ * Allow the system to configure the topology streams<br>+ * before we create the offer SDP.<br>+ */<br>+ sdp_state_cb_offerer_config_topology(sdp_state,<br>+ sdp_state->proposed_capabilities->topology);<br>+<br> sdp_state->local_sdp = sdp_create_from_state(sdp_state, sdp_state->proposed_capabilities);<br>+ break;<br>+ case SDP_ROLE_OFFERER:<br>+ break;<br>+ case SDP_ROLE_ANSWERER:<br>+ if (!sdp_state->local_sdp<br>+ && sdp_state->negotiated_capabilities<br>+ && !sdp_state->remote_offer_rejected) {<br>+ sdp_state->local_sdp = sdp_create_from_state(sdp_state, sdp_state->negotiated_capabilities);<br>+ }<br>+ break;<br> }<br> <br> return sdp_state->local_sdp;<br>@@ -1237,35 +2616,63 @@<br> return -1;<br> }<br> ret = ast_sdp_state_set_remote_sdp(sdp_state, sdp);<br>- ast_sdp_free(sdp);<br>+ ao2_ref(sdp, -1);<br> return ret;<br> }<br> <br>-int ast_sdp_state_reset(struct ast_sdp_state *sdp_state)<br>+int ast_sdp_state_is_offer_rejected(struct ast_sdp_state *sdp_state)<br>+{<br>+ return sdp_state->remote_offer_rejected;<br>+}<br>+<br>+int ast_sdp_state_is_offerer(struct ast_sdp_state *sdp_state)<br>+{<br>+ return sdp_state->role == SDP_ROLE_OFFERER;<br>+}<br>+<br>+int ast_sdp_state_is_answerer(struct ast_sdp_state *sdp_state)<br>+{<br>+ return sdp_state->role == SDP_ROLE_ANSWERER;<br>+}<br>+<br>+int ast_sdp_state_restart_negotiations(struct ast_sdp_state *sdp_state)<br> {<br> ast_assert(sdp_state != NULL);<br> <br>- ast_sdp_free(sdp_state->local_sdp);<br>+ ao2_cleanup(sdp_state->local_sdp);<br> sdp_state->local_sdp = NULL;<br> <br>- set_proposed_capabilities(sdp_state, NULL);<br>-<br> sdp_state->role = SDP_ROLE_NOT_SET;<br>+ sdp_state->remote_offer_rejected = 0;<br>+<br>+ if (sdp_state->negotiated_capabilities) {<br>+ update_proposed_capabilities(sdp_state, sdp_state->negotiated_capabilities);<br>+ }<br> <br> return 0;<br> }<br> <br>-int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *streams)<br>+int ast_sdp_state_update_local_topology(struct ast_sdp_state *sdp_state, struct ast_stream_topology *topology)<br> {<br>- struct sdp_state_capabilities *capabilities;<br>- ast_assert(sdp_state != NULL);<br>- ast_assert(streams != NULL);<br>+ struct ast_stream_topology *merged_topology;<br> <br>- capabilities = merge_capabilities(sdp_state, streams, 1);<br>- if (!capabilities) {<br>- return -1;<br>+ ast_assert(sdp_state != NULL);<br>+ ast_assert(topology != NULL);<br>+<br>+ if (sdp_state->pending_topology_update) {<br>+ merged_topology = merge_local_topologies(sdp_state,<br>+ sdp_state->pending_topology_update, topology, 0);<br>+ if (!merged_topology) {<br>+ return -1;<br>+ }<br>+ ast_stream_topology_free(sdp_state->pending_topology_update);<br>+ sdp_state->pending_topology_update = merged_topology;<br>+ } else {<br>+ sdp_state->pending_topology_update = ast_stream_topology_clone(topology);<br>+ if (!sdp_state->pending_topology_update) {<br>+ return -1;<br>+ }<br> }<br>- set_proposed_capabilities(sdp_state, capabilities);<br> <br> return 0;<br> }<br>@@ -1275,9 +2682,9 @@<br> ast_assert(sdp_state != NULL);<br> <br> if (!address) {<br>- ast_sockaddr_setnull(&sdp_state->proposed_capabilities->connection_address);<br>+ ast_sockaddr_setnull(&sdp_state->connection_address);<br> } else {<br>- ast_sockaddr_copy(&sdp_state->proposed_capabilities->connection_address, address);<br>+ ast_sockaddr_copy(&sdp_state->connection_address, address);<br> }<br> }<br> <br>@@ -1301,18 +2708,37 @@<br> return 0;<br> }<br> <br>+void ast_sdp_state_set_global_locally_held(struct ast_sdp_state *sdp_state, unsigned int locally_held)<br>+{<br>+ ast_assert(sdp_state != NULL);<br>+<br>+ sdp_state->locally_held = locally_held ? 1 : 0;<br>+}<br>+<br>+unsigned int ast_sdp_state_get_global_locally_held(const struct ast_sdp_state *sdp_state)<br>+{<br>+ ast_assert(sdp_state != NULL);<br>+<br>+ return sdp_state->locally_held;<br>+}<br>+<br> void ast_sdp_state_set_locally_held(struct ast_sdp_state *sdp_state,<br> int stream_index, unsigned int locally_held)<br> {<br> struct sdp_state_stream *stream_state;<br> ast_assert(sdp_state != NULL);<br> <br>- stream_state = sdp_state_get_stream(sdp_state, stream_index);<br>- if (!stream_state) {<br>- return;<br>+ locally_held = locally_held ? 1 : 0;<br>+<br>+ stream_state = sdp_state_get_joint_stream(sdp_state, stream_index);<br>+ if (stream_state) {<br>+ stream_state->locally_held = locally_held;<br> }<br> <br>- stream_state->locally_held = locally_held;<br>+ stream_state = sdp_state_get_stream(sdp_state, stream_index);<br>+ if (stream_state) {<br>+ stream_state->locally_held = locally_held;<br>+ }<br> }<br> <br> unsigned int ast_sdp_state_get_locally_held(const struct ast_sdp_state *sdp_state,<br>@@ -1321,12 +2747,27 @@<br> struct sdp_state_stream *stream_state;<br> ast_assert(sdp_state != NULL);<br> <br>- stream_state = sdp_state_get_stream(sdp_state, stream_index);<br>+ stream_state = sdp_state_get_joint_stream(sdp_state, stream_index);<br> if (!stream_state) {<br> return 0;<br> }<br> <br> return stream_state->locally_held;<br>+}<br>+<br>+unsigned int ast_sdp_state_get_remotely_held(const struct ast_sdp_state *sdp_state,<br>+ int stream_index)<br>+{<br>+ struct sdp_state_stream *stream_state;<br>+<br>+ ast_assert(sdp_state != NULL);<br>+<br>+ stream_state = sdp_state_get_joint_stream(sdp_state, stream_index);<br>+ if (!stream_state) {<br>+ return 0;<br>+ }<br>+<br>+ return stream_state->remotely_held;<br> }<br> <br> void ast_sdp_state_set_t38_parameters(struct ast_sdp_state *sdp_state,<br>@@ -1336,11 +2777,9 @@<br> ast_assert(sdp_state != NULL && params != NULL);<br> <br> stream_state = sdp_state_get_stream(sdp_state, stream_index);<br>- if (!stream_state) {<br>- return;<br>+ if (stream_state) {<br>+ stream_state->t38_local_params = *params;<br> }<br>-<br>- stream_state->t38_local_params = *params;<br> }<br> <br> /*!<br>@@ -1398,7 +2837,8 @@<br> caps = ast_stream_get_formats(stream);<br> <br> stream_state = AST_VECTOR_GET(&capabilities->streams, stream_index);<br>- if (stream_state->rtp && caps && ast_format_cap_count(caps)) {<br>+ if (stream_state->rtp && caps && ast_format_cap_count(caps)<br>+ && AST_STREAM_STATE_REMOVED != ast_stream_get_state(stream)) {<br> rtp = stream_state->rtp->instance;<br> } else {<br> /* This is a disabled stream */<br>@@ -1408,7 +2848,7 @@<br> if (rtp) {<br> struct ast_sockaddr address_rtp;<br> <br>- if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_rtp)) {<br>+ if (sdp_state_stream_get_connection_address(sdp_state, stream_state, &address_rtp)) {<br> return -1;<br> }<br> rtp_port = ast_sockaddr_port(&address_rtp);<br>@@ -1426,6 +2866,8 @@<br> }<br> <br> if (rtp_port) {<br>+ const char *direction;<br>+<br> /* Stream is not declined/disabled */<br> for (i = 0; i < ast_format_cap_count(caps); i++) {<br> struct ast_format *format = ast_format_cap_get_format(caps, i);<br>@@ -1502,12 +2944,27 @@<br> }<br> }<br> <br>- a_line = ast_sdp_a_alloc(ast_sdp_state_get_locally_held(sdp_state, stream_index)<br>- ? "sendonly" : "sendrecv", "");<br>- if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {<br>- ast_sdp_a_free(a_line);<br>- ast_sdp_m_free(m_line);<br>- return -1;<br>+ if (sdp_state->locally_held || stream_state->locally_held) {<br>+ if (stream_state->remotely_held) {<br>+ direction = "inactive";<br>+ } else {<br>+ direction = "sendonly";<br>+ }<br>+ } else {<br>+ if (stream_state->remotely_held) {<br>+ direction = "recvonly";<br>+ } else {<br>+ /* Default is "sendrecv" */<br>+ direction = NULL;<br>+ }<br>+ }<br>+ if (direction) {<br>+ a_line = ast_sdp_a_alloc(direction, "");<br>+ if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {<br>+ ast_sdp_a_free(a_line);<br>+ ast_sdp_m_free(m_line);<br>+ return -1;<br>+ }<br> }<br> <br> add_ssrc_attributes(m_line, options, rtp);<br>@@ -1585,7 +3042,8 @@<br> ast_assert(sdp && options && stream);<br> <br> stream_state = AST_VECTOR_GET(&capabilities->streams, stream_index);<br>- if (stream_state->udptl) {<br>+ if (stream_state->udptl<br>+ && AST_STREAM_STATE_REMOVED != ast_stream_get_state(stream)) {<br> udptl = stream_state->udptl;<br> } else {<br> /* This is a disabled stream */<br>@@ -1595,7 +3053,7 @@<br> if (udptl) {<br> struct ast_sockaddr address_udptl;<br> <br>- if (ast_sdp_state_get_stream_connection_address(sdp_state, 0, &address_udptl)) {<br>+ if (sdp_state_stream_get_connection_address(sdp_state, stream_state, &address_udptl)) {<br> return -1;<br> }<br> udptl_port = ast_sockaddr_port(&address_udptl);<br>@@ -1619,8 +3077,6 @@<br> <br> if (udptl_port) {<br> /* Stream is not declined/disabled */<br>- stream_state = sdp_state_get_stream(sdp_state, stream_index);<br>-<br> snprintf(tmp, sizeof(tmp), "%u", stream_state->t38_local_params.version);<br> a_line = ast_sdp_a_alloc("T38FaxVersion", tmp);<br> if (!a_line || ast_sdp_m_add_a(m_line, a_line)) {<br>@@ -1773,7 +3229,6 @@<br> }<br> <br> stream_count = ast_stream_topology_get_count(topology);<br>-<br> for (stream_num = 0; stream_num < stream_count; stream_num++) {<br> switch (ast_stream_get_type(ast_stream_topology_get_stream(topology, stream_num))) {<br> case AST_MEDIA_TYPE_AUDIO:<br>@@ -1798,7 +3253,7 @@<br> <br> error:<br> if (sdp) {<br>- ast_sdp_free(sdp);<br>+ ao2_ref(sdp, -1);<br> } else {<br> ast_sdp_t_free(t_line);<br> ast_sdp_s_free(s_line);<br>diff --git a/main/stream.c b/main/stream.c<br>index 20179f3..b6d3911 100644<br>--- a/main/stream.c<br>+++ b/main/stream.c<br>@@ -214,6 +214,23 @@<br> }<br> }<br> <br>+enum ast_stream_state ast_stream_str2state(const char *str)<br>+{<br>+ if (!strcmp("sendrecv", str)) {<br>+ return AST_STREAM_STATE_SENDRECV;<br>+ }<br>+ if (!strcmp("sendonly", str)) {<br>+ return AST_STREAM_STATE_SENDONLY;<br>+ }<br>+ if (!strcmp("recvonly", str)) {<br>+ return AST_STREAM_STATE_RECVONLY;<br>+ }<br>+ if (!strcmp("inactive", str)) {<br>+ return AST_STREAM_STATE_INACTIVE;<br>+ }<br>+ return AST_STREAM_STATE_REMOVED;<br>+}<br>+<br> void *ast_stream_get_data(struct ast_stream *stream, enum ast_stream_data_slot slot)<br> {<br> ast_assert(stream != NULL);<br>diff --git a/res/res_sdp_translator_pjmedia.c b/res/res_sdp_translator_pjmedia.c<br>index 85f246e..d80f3d5 100644<br>--- a/res/res_sdp_translator_pjmedia.c<br>+++ b/res/res_sdp_translator_pjmedia.c<br>@@ -484,7 +484,7 @@<br> }<br> <br> cleanup:<br>- ast_sdp_free(sdp);<br>+ ao2_cleanup(sdp);<br> ast_sdp_translator_free(translator);<br> pj_pool_release(pool);<br> return res;<br>@@ -560,7 +560,7 @@<br> }<br> <br> cleanup:<br>- ast_sdp_free(sdp);<br>+ ao2_cleanup(sdp);<br> ast_sdp_translator_free(translator);<br> pj_pool_release(pool);<br> return res;<br>diff --git a/tests/test_sdp.c b/tests/test_sdp.c<br>index 7eef3f7..662e2aa 100644<br>--- a/tests/test_sdp.c<br>+++ b/tests/test_sdp.c<br>@@ -89,12 +89,31 @@<br> }<br> <br> if (ast_sdp_m_get_payload_count(m_line) != num_payloads) {<br>- ast_test_status_update(test, "Expected m-line payload count %d but got %d\n",<br>- num_payloads, ast_sdp_m_get_payload_count(m_line));<br>+ ast_test_status_update(test, "Expected %s m-line payload count %d but got %d\n",<br>+ media_type, num_payloads, ast_sdp_m_get_payload_count(m_line));<br> return -1;<br> }<br> <br>- ast_test_status_update(test, "SDP m-line is as expected\n");<br>+ ast_test_status_update(test, "SDP %s m-line is as expected\n", media_type);<br>+ return 0;<br>+}<br>+<br>+static int validate_m_line_declined(struct ast_test *test,<br>+ const struct ast_sdp_m_line *m_line, const char *media_type)<br>+{<br>+ if (strcmp(m_line->type, media_type)) {<br>+ ast_test_status_update(test, "Expected m-line media type %s but got %s\n",<br>+ media_type, m_line->type);<br>+ return -1;<br>+ }<br>+<br>+ if (m_line->port != 0) {<br>+ ast_test_status_update(test, "Expected %s m-line to be declined but got port %u\n",<br>+ media_type, m_line->port);<br>+ return -1;<br>+ }<br>+<br>+ ast_test_status_update(test, "SDP %s m-line is as expected\n", media_type);<br> return 0;<br> }<br> <br>@@ -438,6 +457,26 @@<br> const char *formats;<br> };<br> <br>+static int build_sdp_option_formats(struct ast_sdp_options *options, int num_streams, const struct sdp_format *formats)<br>+{<br>+ int idx;<br>+<br>+ for (idx = 0; idx < num_streams; ++idx) {<br>+ RAII_VAR(struct ast_format_cap *, caps, NULL, ao2_cleanup);<br>+<br>+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+ if (!caps) {<br>+ return -1;<br>+ }<br>+<br>+ if (ast_format_cap_update_by_allow_disallow(caps, formats[idx].formats, 1) < 0) {<br>+ return -1;<br>+ }<br>+ ast_sdp_options_set_format_cap_type(options, formats[idx].type, caps);<br>+ }<br>+ return 0;<br>+}<br>+<br> /*!<br> * \brief Common method to build an SDP state for a test.<br> *<br>@@ -450,9 +489,16 @@<br> *<br> * \param num_streams The number of elements in the formats array.<br> * \param formats Array of media types and formats that will be in the state.<br>+ * \param opt_num_streams The number of new stream types allowed to create.<br>+ * Not used if test_options provided.<br>+ * \param opt_formats Array of new stream media types and formats allowed to create.<br>+ * NULL if use a default stream creation.<br>+ * Not used if test_options provided.<br> * \param test_options Optional SDP options.<br> */<br>-static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats, struct ast_sdp_options *test_options)<br>+static struct ast_sdp_state *build_sdp_state(int num_streams, const struct sdp_format *formats,<br>+ int opt_num_streams, const struct sdp_format *opt_formats,<br>+ struct ast_sdp_options *test_options)<br> {<br> struct ast_stream_topology *topology = NULL;<br> struct ast_sdp_state *state = NULL;<br>@@ -460,8 +506,32 @@<br> int i;<br> <br> if (!test_options) {<br>+ unsigned int max_streams;<br>+<br>+ static const struct sdp_format sdp_formats[] = {<br>+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },<br>+ { AST_MEDIA_TYPE_VIDEO, "vp8" },<br>+ { AST_MEDIA_TYPE_IMAGE, "t38" },<br>+ };<br>+<br> options = sdp_options_common();<br> if (!options) {<br>+ goto end;<br>+ }<br>+<br>+ /* Determine max_streams to allow */<br>+ max_streams = ARRAY_LEN(sdp_formats);<br>+ if (ARRAY_LEN(sdp_formats) < num_streams) {<br>+ max_streams = num_streams;<br>+ }<br>+ ast_sdp_options_set_max_streams(options, max_streams);<br>+<br>+ /* Determine new stream formats and types allowed */<br>+ if (!opt_formats) {<br>+ opt_num_streams = ARRAY_LEN(sdp_formats);<br>+ opt_formats = sdp_formats;<br>+ }<br>+ if (build_sdp_option_formats(options, opt_num_streams, opt_formats)) {<br> goto end;<br> }<br> } else {<br>@@ -489,7 +559,10 @@<br> goto end;<br> }<br> ast_stream_set_formats(stream, caps);<br>- ast_stream_topology_append_stream(topology, stream);<br>+ if (ast_stream_topology_append_stream(topology, stream) < 0) {<br>+ ast_stream_free(stream);<br>+ goto end;<br>+ }<br> }<br> <br> state = ast_sdp_state_alloc(topology, options);<br>@@ -530,7 +603,8 @@<br> break;<br> }<br> <br>- sdp_state = build_sdp_state(ARRAY_LEN(formats), formats, NULL);<br>+ sdp_state = build_sdp_state(ARRAY_LEN(formats), formats,<br>+ ARRAY_LEN(formats), formats, NULL);<br> if (!sdp_state) {<br> goto end;<br> }<br>@@ -674,7 +748,8 @@<br> break;<br> }<br> <br>- sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats, NULL);<br>+ sdp_state = build_sdp_state(ARRAY_LEN(sdp_formats), sdp_formats,<br>+ ARRAY_LEN(sdp_formats), sdp_formats, NULL);<br> if (!sdp_state) {<br> res = AST_TEST_FAIL;<br> goto end;<br>@@ -723,7 +798,7 @@<br> return res;<br> }<br> <br>-static int validate_merged_sdp(struct ast_test *test, const struct ast_sdp *sdp)<br>+static int validate_avi_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp)<br> {<br> struct ast_sdp_m_line *m_line;<br> <br>@@ -769,7 +844,11 @@<br> return 0;<br> }<br> <br>-AST_TEST_DEFINE(sdp_merge_symmetric)<br>+static enum ast_test_result_state sdp_negotiation_completed_tests(struct ast_test *test,<br>+ int offer_num_streams, const struct sdp_format *offer_formats,<br>+ int answer_num_streams, const struct sdp_format *answer_formats,<br>+ int allowed_ans_num_streams, const struct sdp_format *allowed_ans_formats,<br>+ int (*validate_sdp)(struct ast_test *test, const struct ast_sdp *sdp))<br> {<br> enum ast_test_result_state res = AST_TEST_PASS;<br> struct ast_sdp_state *sdp_state_offerer = NULL;<br>@@ -777,38 +856,15 @@<br> const struct ast_sdp *offerer_sdp;<br> const struct ast_sdp *answerer_sdp;<br> <br>- static const struct sdp_format offerer_formats[] = {<br>- { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },<br>- { AST_MEDIA_TYPE_VIDEO, "h264,vp8" },<br>- { AST_MEDIA_TYPE_IMAGE, "t38" },<br>- };<br>- static const struct sdp_format answerer_formats[] = {<br>- { AST_MEDIA_TYPE_AUDIO, "ulaw" },<br>- { AST_MEDIA_TYPE_VIDEO, "vp8" },<br>- { AST_MEDIA_TYPE_IMAGE, "t38" },<br>- };<br>-<br>- switch(cmd) {<br>- case TEST_INIT:<br>- info->name = "sdp_merge_symmetric";<br>- info->category = "/main/sdp/";<br>- info->summary = "Merge two SDPs with symmetric stream types";<br>- info->description =<br>- "SDPs 1 and 2 each have one audio and one video stream (in that order).\n"<br>- "SDP 1 offers to SDP 2, who answers. We ensure that both local SDPs have\n"<br>- "the expected stream types and the expected formats";<br>- return AST_TEST_NOT_RUN;<br>- case TEST_EXECUTE:<br>- break;<br>- }<br>-<br>- sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL);<br>+ sdp_state_offerer = build_sdp_state(offer_num_streams, offer_formats,<br>+ offer_num_streams, offer_formats, NULL);<br> if (!sdp_state_offerer) {<br> res = AST_TEST_FAIL;<br> goto end;<br> }<br> <br>- sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL);<br>+ sdp_state_answerer = build_sdp_state(answer_num_streams, answer_formats,<br>+ allowed_ans_num_streams, allowed_ans_formats, NULL);<br> if (!sdp_state_answerer) {<br> res = AST_TEST_FAIL;<br> goto end;<br>@@ -820,22 +876,37 @@<br> goto end;<br> }<br> <br>- ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp);<br>+ if (ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp)) {<br>+ res = AST_TEST_FAIL;<br>+ goto end;<br>+ }<br> answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer);<br> if (!answerer_sdp) {<br> res = AST_TEST_FAIL;<br> goto end;<br> }<br> <br>- ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp);<br>-<br>- /* Get the offerer SDP again because it's now going to be the joint SDP */<br>- offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);<br>- if (validate_merged_sdp(test, offerer_sdp)) {<br>+ if (ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp)) {<br> res = AST_TEST_FAIL;<br> goto end;<br> }<br>- if (validate_merged_sdp(test, answerer_sdp)) {<br>+<br>+ /*<br>+ * Restart SDP negotiations to build the joint SDP on the offerer<br>+ * side. Otherwise we will get the original offer for use in<br>+ * case of retransmissions.<br>+ */<br>+ if (ast_sdp_state_restart_negotiations(sdp_state_offerer)) {<br>+ ast_test_status_update(test, "Restarting negotiations failed\n");<br>+ res = AST_TEST_FAIL;<br>+ goto end;<br>+ }<br>+ offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);<br>+ if (validate_sdp(test, offerer_sdp)) {<br>+ res = AST_TEST_FAIL;<br>+ goto end;<br>+ }<br>+ if (validate_sdp(test, answerer_sdp)) {<br> res = AST_TEST_FAIL;<br> goto end;<br> }<br>@@ -847,14 +918,37 @@<br> return res;<br> }<br> <br>-AST_TEST_DEFINE(sdp_merge_crisscross)<br>+AST_TEST_DEFINE(sdp_negotiation_initial)<br> {<br>- enum ast_test_result_state res = AST_TEST_PASS;<br>- struct ast_sdp_state *sdp_state_offerer = NULL;<br>- struct ast_sdp_state *sdp_state_answerer = NULL;<br>- const struct ast_sdp *offerer_sdp;<br>- const struct ast_sdp *answerer_sdp;<br>+ static const struct sdp_format offerer_formats[] = {<br>+ { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },<br>+ { AST_MEDIA_TYPE_VIDEO, "h264,vp8" },<br>+ { AST_MEDIA_TYPE_IMAGE, "t38" },<br>+ };<br> <br>+ switch(cmd) {<br>+ case TEST_INIT:<br>+ info->name = "sdp_negotiation_initial";<br>+ info->category = "/main/sdp/";<br>+ info->summary = "Simulate an initial negotiation";<br>+ info->description =<br>+ "Initial negotiation tests creating new streams on the answering side.\n"<br>+ "After negotiation both offerer and answerer sides should have the same\n"<br>+ "expected stream types and formats.";<br>+ return AST_TEST_NOT_RUN;<br>+ case TEST_EXECUTE:<br>+ break;<br>+ }<br>+<br>+ return sdp_negotiation_completed_tests(test,<br>+ ARRAY_LEN(offerer_formats), offerer_formats,<br>+ 0, NULL,<br>+ 0, NULL,<br>+ validate_avi_sdp_streams);<br>+}<br>+<br>+AST_TEST_DEFINE(sdp_negotiation_type_change)<br>+{<br> static const struct sdp_format offerer_formats[] = {<br> { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },<br> { AST_MEDIA_TYPE_VIDEO, "h264,vp8" },<br>@@ -868,82 +962,45 @@<br> <br> switch(cmd) {<br> case TEST_INIT:<br>- info->name = "sdp_merge_crisscross";<br>+ info->name = "sdp_negotiation_type_change";<br> info->category = "/main/sdp/";<br>- info->summary = "Merge two SDPs with symmetric stream types";<br>+ info->summary = "Simulate a re-negotiation changing stream types";<br> info->description =<br>- "SDPs 1 and 2 each have one audio and one video stream. However, SDP 1 and\n"<br>- "2 natively have the formats in a different order.\n"<br>- "SDP 1 offers to SDP 2, who answers. We ensure that both local SDPs have\n"<br>- "the expected stream types and the expected formats. Since SDP 1 was the\n"<br>- "offerer, the format order on SDP 1 should determine the order of formats in the SDPs";<br>+ "Reinvite negotiation tests changing stream types on the answering side.\n"<br>+ "After negotiation both offerer and answerer sides should have the same\n"<br>+ "expected stream types and formats.";<br> return AST_TEST_NOT_RUN;<br> case TEST_EXECUTE:<br> break;<br> }<br> <br>- sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL);<br>- if (!sdp_state_offerer) {<br>- res = AST_TEST_FAIL;<br>- goto end;<br>- }<br>-<br>- sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL);<br>- if (!sdp_state_answerer) {<br>- res = AST_TEST_FAIL;<br>- goto end;<br>- }<br>-<br>- offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);<br>- if (!offerer_sdp) {<br>- res = AST_TEST_FAIL;<br>- goto end;<br>- }<br>-<br>- ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp);<br>- answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer);<br>- if (!answerer_sdp) {<br>- res = AST_TEST_FAIL;<br>- goto end;<br>- }<br>-<br>- ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp);<br>-<br>- /* Get the offerer SDP again because it's now going to be the joint SDP */<br>- offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);<br>- if (validate_merged_sdp(test, offerer_sdp)) {<br>- res = AST_TEST_FAIL;<br>- goto end;<br>- }<br>- if (validate_merged_sdp(test, answerer_sdp)) {<br>- res = AST_TEST_FAIL;<br>- goto end;<br>- }<br>-<br>-end:<br>- ast_sdp_state_free(sdp_state_offerer);<br>- ast_sdp_state_free(sdp_state_answerer);<br>-<br>- return res;<br>+ return sdp_negotiation_completed_tests(test,<br>+ ARRAY_LEN(offerer_formats), offerer_formats,<br>+ ARRAY_LEN(answerer_formats), answerer_formats,<br>+ 0, NULL,<br>+ validate_avi_sdp_streams);<br> }<br> <br>-static int validate_merged_sdp_asymmetric(struct ast_test *test, const struct ast_sdp *sdp, int is_offer)<br>+static int validate_ava_declined_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp)<br> {<br> struct ast_sdp_m_line *m_line;<br>- const char *side = is_offer ? "Offer side" : "Answer side";<br> <br> if (!sdp) {<br>- ast_test_status_update(test, "%s does not have a SDP\n", side);<br> return -1;<br> }<br> <br>- /* Stream 0 */<br> m_line = ast_sdp_get_m(sdp, 0);<br>- if (validate_m_line(test, m_line, "audio", 1)) {<br>+ if (validate_m_line_declined(test, m_line, "audio")) {<br> return -1;<br> }<br>- if (!m_line->port) {<br>- ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 0, "n't");<br>+<br>+ m_line = ast_sdp_get_m(sdp, 1);<br>+ if (validate_m_line_declined(test, m_line, "video")) {<br>+ return -1;<br>+ }<br>+<br>+ m_line = ast_sdp_get_m(sdp, 2);<br>+ if (validate_m_line(test, m_line, "audio", 1)) {<br> return -1;<br> }<br> if (validate_rtpmap(test, m_line, "PCMU")) {<br>@@ -954,61 +1011,16 @@<br> if (!validate_rtpmap(test, m_line, "PCMA")) {<br> return -1;<br> }<br>- if (!validate_rtpmap(test, m_line, "G722")) {<br>- return -1;<br>- }<br>- if (!validate_rtpmap(test, m_line, "opus")) {<br>- return -1;<br>- }<br>-<br>- /* The remaining streams should be declined */<br>-<br>- /* Stream 1 */<br>- m_line = ast_sdp_get_m(sdp, 1);<br>- if (validate_m_line(test, m_line, "audio", 1)) {<br>- return -1;<br>- }<br>- if (m_line->port) {<br>- ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 1, "");<br>- return -1;<br>- }<br>-<br>- /* Stream 2 */<br>- m_line = ast_sdp_get_m(sdp, 2);<br>- if (validate_m_line(test, m_line, "video", 1)) {<br>- return -1;<br>- }<br>- if (m_line->port) {<br>- ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 2, "");<br>- return -1;<br>- }<br>-<br>- /* Stream 3 */<br>- m_line = ast_sdp_get_m(sdp, 3);<br>- if (validate_m_line(test, m_line, "image", 1)) {<br>- return -1;<br>- }<br>- if (m_line->port) {<br>- ast_test_status_update(test, "%s stream %d does%s have a port\n", side, 3, "");<br>- return -1;<br>- }<br> <br> return 0;<br> }<br> <br>-AST_TEST_DEFINE(sdp_merge_asymmetric)<br>+AST_TEST_DEFINE(sdp_negotiation_decline_incompatible)<br> {<br>- enum ast_test_result_state res = AST_TEST_PASS;<br>- struct ast_sdp_state *sdp_state_offerer = NULL;<br>- struct ast_sdp_state *sdp_state_answerer = NULL;<br>- const struct ast_sdp *offerer_sdp;<br>- const struct ast_sdp *answerer_sdp;<br>-<br> static const struct sdp_format offerer_formats[] = {<br>- { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,opus" },<br>- { AST_MEDIA_TYPE_AUDIO, "ulaw" },<br>- { AST_MEDIA_TYPE_VIDEO, "h261" },<br>- { AST_MEDIA_TYPE_IMAGE, "t38" },<br>+ { AST_MEDIA_TYPE_AUDIO, "alaw" },<br>+ { AST_MEDIA_TYPE_VIDEO, "vp8" },<br>+ { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw" },<br> };<br> static const struct sdp_format answerer_formats[] = {<br> { AST_MEDIA_TYPE_AUDIO, "ulaw" },<br>@@ -1016,26 +1028,128 @@<br> <br> switch(cmd) {<br> case TEST_INIT:<br>- info->name = "sdp_merge_asymmetric";<br>+ info->name = "sdp_negotiation_decline_incompatible";<br> info->category = "/main/sdp/";<br>- info->summary = "Merge two SDPs with an asymmetric number of streams";<br>+ info->summary = "Simulate an initial negotiation declining streams";<br> info->description =<br>- "SDP 1 offers a four stream topology: Audio,Audio,Video,T.38\n"<br>- "SDP 2 only has a single audio stream topology\n"<br>- "We ensure that both local SDPs have the expected stream types and\n"<br>- "the expected declined streams";<br>+ "Initial negotiation tests declining incompatible streams on the answering side.\n"<br>+ "After negotiation both offerer and answerer sides should have the same\n"<br>+ "expected stream types and formats.";<br> return AST_TEST_NOT_RUN;<br> case TEST_EXECUTE:<br> break;<br> }<br> <br>- sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats, NULL);<br>+ return sdp_negotiation_completed_tests(test,<br>+ ARRAY_LEN(offerer_formats), offerer_formats,<br>+ ARRAY_LEN(answerer_formats), answerer_formats,<br>+ ARRAY_LEN(answerer_formats), answerer_formats,<br>+ validate_ava_declined_sdp_streams);<br>+}<br>+<br>+static int validate_aaaa_declined_sdp_streams(struct ast_test *test, const struct ast_sdp *sdp)<br>+{<br>+ struct ast_sdp_m_line *m_line;<br>+<br>+ if (!sdp) {<br>+ return -1;<br>+ }<br>+<br>+ m_line = ast_sdp_get_m(sdp, 0);<br>+ if (validate_m_line(test, m_line, "audio", 1)) {<br>+ return -1;<br>+ }<br>+ if (validate_rtpmap(test, m_line, "PCMU")) {<br>+ return -1;<br>+ }<br>+<br>+ m_line = ast_sdp_get_m(sdp, 1);<br>+ if (validate_m_line(test, m_line, "audio", 1)) {<br>+ return -1;<br>+ }<br>+ if (validate_rtpmap(test, m_line, "PCMU")) {<br>+ return -1;<br>+ }<br>+<br>+ m_line = ast_sdp_get_m(sdp, 2);<br>+ if (validate_m_line(test, m_line, "audio", 1)) {<br>+ return -1;<br>+ }<br>+ if (validate_rtpmap(test, m_line, "PCMU")) {<br>+ return -1;<br>+ }<br>+<br>+ m_line = ast_sdp_get_m(sdp, 3);<br>+ if (validate_m_line_declined(test, m_line, "audio")) {<br>+ return -1;<br>+ }<br>+<br>+ return 0;<br>+}<br>+<br>+AST_TEST_DEFINE(sdp_negotiation_decline_max_streams)<br>+{<br>+ static const struct sdp_format offerer_formats[] = {<br>+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },<br>+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },<br>+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },<br>+ { AST_MEDIA_TYPE_AUDIO, "ulaw" },<br>+ };<br>+<br>+ switch(cmd) {<br>+ case TEST_INIT:<br>+ info->name = "sdp_negotiation_decline_max_streams";<br>+ info->category = "/main/sdp/";<br>+ info->summary = "Simulate an initial negotiation declining excessive streams";<br>+ info->description =<br>+ "Initial negotiation tests declining too many streams on the answering side.\n"<br>+ "After negotiation both offerer and answerer sides should have the same\n"<br>+ "expected stream types and formats.";<br>+ return AST_TEST_NOT_RUN;<br>+ case TEST_EXECUTE:<br>+ break;<br>+ }<br>+<br>+ return sdp_negotiation_completed_tests(test,<br>+ ARRAY_LEN(offerer_formats), offerer_formats,<br>+ 0, NULL,<br>+ 0, NULL,<br>+ validate_aaaa_declined_sdp_streams);<br>+}<br>+<br>+AST_TEST_DEFINE(sdp_negotiation_not_acceptable)<br>+{<br>+ enum ast_test_result_state res = AST_TEST_PASS;<br>+ struct ast_sdp_state *sdp_state_offerer = NULL;<br>+ struct ast_sdp_state *sdp_state_answerer = NULL;<br>+ const struct ast_sdp *offerer_sdp;<br>+<br>+ static const struct sdp_format offerer_formats[] = {<br>+ { AST_MEDIA_TYPE_AUDIO, "alaw" },<br>+ { AST_MEDIA_TYPE_AUDIO, "alaw" },<br>+ };<br>+<br>+ switch(cmd) {<br>+ case TEST_INIT:<br>+ info->name = "sdp_negotiation_not_acceptable";<br>+ info->category = "/main/sdp/";<br>+ info->summary = "Simulate an initial negotiation declining all streams";<br>+ info->description =<br>+ "Initial negotiation tests declining all streams for a 488 on the answering side.\n"<br>+ "Negotiations should fail because there are no acceptable streams.";<br>+ return AST_TEST_NOT_RUN;<br>+ case TEST_EXECUTE:<br>+ break;<br>+ }<br>+<br>+ sdp_state_offerer = build_sdp_state(ARRAY_LEN(offerer_formats), offerer_formats,<br>+ ARRAY_LEN(offerer_formats), offerer_formats, NULL);<br> if (!sdp_state_offerer) {<br> res = AST_TEST_FAIL;<br> goto end;<br> }<br> <br>- sdp_state_answerer = build_sdp_state(ARRAY_LEN(answerer_formats), answerer_formats, NULL);<br>+ sdp_state_answerer = build_sdp_state(0, NULL, 0, NULL, NULL);<br> if (!sdp_state_answerer) {<br> res = AST_TEST_FAIL;<br> goto end;<br>@@ -1047,24 +1161,15 @@<br> goto end;<br> }<br> <br>- ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp);<br>- answerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_answerer);<br>- if (!answerer_sdp) {<br>+ if (!ast_sdp_state_set_remote_sdp(sdp_state_answerer, offerer_sdp)) {<br>+ ast_test_status_update(test, "Bad. Setting remote SDP was successful.\n");<br> res = AST_TEST_FAIL;<br> goto end;<br> }<br>-<br>- ast_sdp_state_set_remote_sdp(sdp_state_offerer, answerer_sdp);<br>-<br>-#if defined(XXX_TODO_NEED_TO_HANDLE_DECLINED_STREAMS_ON_OFFER_SIDE)<br>- /* Get the offerer SDP again because it's now going to be the joint SDP */<br>- offerer_sdp = ast_sdp_state_get_local_sdp(sdp_state_offerer);<br>- if (validate_merged_sdp_asymmetric(test, offerer_sdp, 1)) {<br>+ if (!ast_sdp_state_is_offer_rejected(sdp_state_answerer)) {<br>+ ast_test_status_update(test, "Bad. Negotiation failed for some other reason.\n");<br> res = AST_TEST_FAIL;<br>- }<br>-#endif<br>- if (validate_merged_sdp_asymmetric(test, answerer_sdp, 0)) {<br>- res = AST_TEST_FAIL;<br>+ goto end;<br> }<br> <br> end:<br>@@ -1135,9 +1240,12 @@<br> ast_test_status_update(test, "Failed to allocate SDP options\n");<br> goto end;<br> }<br>+ if (build_sdp_option_formats(options, ARRAY_LEN(formats), formats)) {<br>+ goto end;<br>+ }<br> ast_sdp_options_set_ssrc(options, 1);<br> <br>- test_state = build_sdp_state(ARRAY_LEN(formats), formats, options);<br>+ test_state = build_sdp_state(ARRAY_LEN(formats), formats, 0, NULL, options);<br> if (!test_state) {<br> ast_test_status_update(test, "Failed to create SDP state\n");<br> goto end;<br>@@ -1179,6 +1287,726 @@<br> return res;<br> }<br> <br>+struct sdp_topology_stream {<br>+ /*! Media stream type: audio, video, image */<br>+ enum ast_media_type type;<br>+ /*! Media stream state: removed/declined, sendrecv */<br>+ enum ast_stream_state state;<br>+ /*! Comma separated list of formats allowed on the stream. Can be NULL if stream is removed/declined. */<br>+ const char *formats;<br>+ /*! Optional name of stream. NULL for default name. */<br>+ const char *name;<br>+};<br>+<br>+struct sdp_update_test {<br>+ /*! Maximum number of streams. (0 if default) */<br>+ int max_streams;<br>+ /*! Optional initial SDP state topology (NULL if not present) */<br>+ const struct sdp_topology_stream * const *initial;<br>+ /*! Required first topology update */<br>+ const struct sdp_topology_stream * const *update_1;<br>+ /*! Optional second topology update (NULL if not present) */<br>+ const struct sdp_topology_stream * const *update_2;<br>+ /*! Expected topology to be offered */<br>+ const struct sdp_topology_stream * const *expected;<br>+};<br>+<br>+static struct ast_stream_topology *build_update_topology(const struct sdp_topology_stream * const *spec)<br>+{<br>+ struct ast_stream_topology *topology;<br>+ const struct sdp_topology_stream *desc;<br>+<br>+ topology = ast_stream_topology_alloc();<br>+ if (!topology) {<br>+ return NULL;<br>+ }<br>+<br>+ for (desc = *spec; desc; ++spec, desc = *spec) {<br>+ struct ast_stream *stream;<br>+ const char *name;<br>+<br>+ name = desc->name ?: ast_codec_media_type2str(desc->type);<br>+ stream = ast_stream_alloc(name, desc->type);<br>+ if (!stream) {<br>+ goto fail;<br>+ }<br>+ ast_stream_set_state(stream, desc->state);<br>+ if (desc->formats) {<br>+ struct ast_format_cap *caps;<br>+<br>+ caps = ast_format_cap_alloc(AST_FORMAT_CAP_FLAG_DEFAULT);<br>+ if (!caps) {<br>+ goto fail;<br>+ }<br>+ if (ast_format_cap_update_by_allow_disallow(caps, desc->formats, 1) < 0) {<br>+ ao2_ref(caps, -1);<br>+ goto fail;<br>+ }<br>+ ast_stream_set_formats(stream, caps);<br>+ ao2_ref(caps, -1);<br>+ }<br>+ if (ast_stream_topology_append_stream(topology, stream) < 0) {<br>+ ast_stream_free(stream);<br>+ goto fail;<br>+ }<br>+ }<br>+ return topology;<br>+<br>+fail:<br>+ ast_stream_topology_free(topology);<br>+ return NULL;<br>+}<br>+<br>+static int cmp_update_topology(struct ast_test *test,<br>+ const struct ast_stream_topology *expected, const struct ast_stream_topology *merged)<br>+{<br>+ int status = 0;<br>+ int idx;<br>+ int max_streams;<br>+ struct ast_stream *exp_stream;<br>+ struct ast_stream *mrg_stream;<br>+<br>+ idx = ast_stream_topology_get_count(expected);<br>+ max_streams = ast_stream_topology_get_count(merged);<br>+ if (idx != max_streams) {<br>+ ast_test_status_update(test, "Expected %d streams got %d streams\n",<br>+ idx, max_streams);<br>+ status = -1;<br>+ }<br>+ if (idx < max_streams) {<br>+ max_streams = idx;<br>+ }<br>+<br>+ /* Compare common streams by position */<br>+ for (idx = 0; idx < max_streams; ++idx) {<br>+ exp_stream = ast_stream_topology_get_stream(expected, idx);<br>+ mrg_stream = ast_stream_topology_get_stream(merged, idx);<br>+<br>+ if (strcmp(ast_stream_get_name(exp_stream), ast_stream_get_name(mrg_stream))) {<br>+ ast_test_status_update(test,<br>+ "Stream %d: Expected stream name '%s' got stream name '%s'\n",<br>+ idx,<br>+ ast_stream_get_name(exp_stream),<br>+ ast_stream_get_name(mrg_stream));<br>+ status = -1;<br>+ }<br>+<br>+ if (ast_stream_get_state(exp_stream) != ast_stream_get_state(mrg_stream)) {<br>+ ast_test_status_update(test,<br>+ "Stream %d: Expected stream state '%s' got stream state '%s'\n",<br>+ idx,<br>+ ast_stream_state2str(ast_stream_get_state(exp_stream)),<br>+ ast_stream_state2str(ast_stream_get_state(mrg_stream)));<br>+ status = -1;<br>+ }<br>+<br>+ if (ast_stream_get_type(exp_stream) != ast_stream_get_type(mrg_stream)) {<br>+ ast_test_status_update(test,<br>+ "Stream %d: Expected stream type '%s' got stream type '%s'\n",<br>+ idx,<br>+ ast_codec_media_type2str(ast_stream_get_type(exp_stream)),<br>+ ast_codec_media_type2str(ast_stream_get_type(mrg_stream)));<br>+ status = -1;<br>+ continue;<br>+ }<br>+<br>+ if (ast_stream_get_state(exp_stream) == AST_STREAM_STATE_REMOVED<br>+ || ast_stream_get_state(mrg_stream) == AST_STREAM_STATE_REMOVED) {<br>+ /*<br>+ * Cannot compare formats if one of the streams is<br>+ * declined because there may not be any on the declined<br>+ * stream.<br>+ */<br>+ continue;<br>+ }<br>+ if (!ast_format_cap_identical(ast_stream_get_formats(exp_stream),<br>+ ast_stream_get_formats(mrg_stream))) {<br>+ ast_test_status_update(test,<br>+ "Stream %d: Expected formats do not match merged formats\n",<br>+ idx);<br>+ status = -1;<br>+ }<br>+ }<br>+<br>+ return status;<br>+}<br>+<br>+<br>+static const struct sdp_topology_stream audio_declined_no_name = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_REMOVED, NULL, NULL<br>+};<br>+<br>+static const struct sdp_topology_stream audio_ulaw_no_name = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", NULL<br>+};<br>+<br>+static const struct sdp_topology_stream audio_alaw_no_name = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", NULL<br>+};<br>+<br>+static const struct sdp_topology_stream audio_g722_no_name = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", NULL<br>+};<br>+<br>+static const struct sdp_topology_stream audio_g723_no_name = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g723", NULL<br>+};<br>+<br>+static const struct sdp_topology_stream video_declined_no_name = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_REMOVED, NULL, NULL<br>+};<br>+<br>+static const struct sdp_topology_stream video_h261_no_name = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", NULL<br>+};<br>+<br>+static const struct sdp_topology_stream video_h263_no_name = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", NULL<br>+};<br>+<br>+static const struct sdp_topology_stream video_h264_no_name = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", NULL<br>+};<br>+<br>+static const struct sdp_topology_stream video_vp8_no_name = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "vp8", NULL<br>+};<br>+<br>+static const struct sdp_topology_stream image_declined_no_name = {<br>+ AST_MEDIA_TYPE_IMAGE, AST_STREAM_STATE_REMOVED, NULL, NULL<br>+};<br>+<br>+static const struct sdp_topology_stream image_t38_no_name = {<br>+ AST_MEDIA_TYPE_IMAGE, AST_STREAM_STATE_SENDRECV, "t38", NULL<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_ulaw_alaw_h264__vp8[] = {<br>+ &audio_ulaw_no_name,<br>+ &audio_alaw_no_name,<br>+ &video_h264_no_name,<br>+ &video_vp8_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top__vp8_alaw_h264_ulaw[] = {<br>+ &video_vp8_no_name,<br>+ &audio_alaw_no_name,<br>+ &video_h264_no_name,<br>+ &audio_ulaw_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_alaw_ulaw__vp8_h264[] = {<br>+ &audio_alaw_no_name,<br>+ &audio_ulaw_no_name,<br>+ &video_vp8_no_name,<br>+ &video_h264_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type with no new or deleted streams */<br>+static const struct sdp_update_test mrg_by_type_00 = {<br>+ .initial = top_ulaw_alaw_h264__vp8,<br>+ .update_1 = top__vp8_alaw_h264_ulaw,<br>+ .expected = top_alaw_ulaw__vp8_h264,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_alaw__vp8[] = {<br>+ &audio_alaw_no_name,<br>+ &video_vp8_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_h264__vp8_ulaw[] = {<br>+ &video_h264_no_name,<br>+ &video_vp8_no_name,<br>+ &audio_ulaw_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_ulaw_h264__vp8[] = {<br>+ &audio_ulaw_no_name,<br>+ &video_h264_no_name,<br>+ &video_vp8_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type and adding a stream */<br>+static const struct sdp_update_test mrg_by_type_01 = {<br>+ .initial = top_alaw__vp8,<br>+ .update_1 = top_h264__vp8_ulaw,<br>+ .expected = top_ulaw_h264__vp8,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_alaw__vp8_vdec[] = {<br>+ &audio_alaw_no_name,<br>+ &video_vp8_no_name,<br>+ &video_declined_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type and deleting a stream */<br>+static const struct sdp_update_test mrg_by_type_02 = {<br>+ .initial = top_ulaw_h264__vp8,<br>+ .update_1 = top_alaw__vp8,<br>+ .expected = top_alaw__vp8_vdec,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_h264_alaw_ulaw[] = {<br>+ &video_h264_no_name,<br>+ &audio_alaw_no_name,<br>+ &audio_ulaw_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top__t38[] = {<br>+ &image_t38_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_vdec__t38_adec[] = {<br>+ &video_declined_no_name,<br>+ &image_t38_no_name,<br>+ &audio_declined_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type changing stream types for T.38 */<br>+static const struct sdp_update_test mrg_by_type_03 = {<br>+ .initial = top_h264_alaw_ulaw,<br>+ .update_1 = top__t38,<br>+ .expected = top_vdec__t38_adec,<br>+};<br>+<br>+<br>+/* Sorting by type changing stream types back from T.38 */<br>+static const struct sdp_update_test mrg_by_type_04 = {<br>+ .initial = top_vdec__t38_adec,<br>+ .update_1 = top_h264_alaw_ulaw,<br>+ .expected = top_h264_alaw_ulaw,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_h264[] = {<br>+ &video_h264_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_vdec__t38[] = {<br>+ &video_declined_no_name,<br>+ &image_t38_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type changing stream types for T.38 */<br>+static const struct sdp_update_test mrg_by_type_05 = {<br>+ .initial = top_h264,<br>+ .update_1 = top__t38,<br>+ .expected = top_vdec__t38,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_h264_idec[] = {<br>+ &video_h264_no_name,<br>+ &image_declined_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type changing stream types back from T.38 */<br>+static const struct sdp_update_test mrg_by_type_06 = {<br>+ .initial = top_vdec__t38,<br>+ .update_1 = top_h264,<br>+ .expected = top_h264_idec,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_ulaw_adec_h264__vp8[] = {<br>+ &audio_ulaw_no_name,<br>+ &audio_declined_no_name,<br>+ &video_h264_no_name,<br>+ &video_vp8_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_h263_alaw_h261_h264_vp8[] = {<br>+ &video_h263_no_name,<br>+ &audio_alaw_no_name,<br>+ &video_h261_no_name,<br>+ &video_h264_no_name,<br>+ &video_vp8_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_alaw_h264_h263_h261_vp8[] = {<br>+ &audio_alaw_no_name,<br>+ &video_h264_no_name,<br>+ &video_h263_no_name,<br>+ &video_h261_no_name,<br>+ &video_vp8_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type with backfill and adding streams */<br>+static const struct sdp_update_test mrg_by_type_07 = {<br>+ .initial = top_ulaw_adec_h264__vp8,<br>+ .update_1 = top_h263_alaw_h261_h264_vp8,<br>+ .expected = top_alaw_h264_h263_h261_vp8,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_ulaw_alaw_h264__vp8_h261[] = {<br>+ &audio_ulaw_no_name,<br>+ &audio_alaw_no_name,<br>+ &video_h264_no_name,<br>+ &video_vp8_no_name,<br>+ &video_h261_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type overlimit of 4 and drop */<br>+static const struct sdp_update_test mrg_by_type_08 = {<br>+ .max_streams = 4,<br>+ .initial = top_ulaw_alaw_h264__vp8,<br>+ .update_1 = top_ulaw_alaw_h264__vp8_h261,<br>+ .expected = top_ulaw_alaw_h264__vp8,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_ulaw_alaw_h264[] = {<br>+ &audio_ulaw_no_name,<br>+ &audio_alaw_no_name,<br>+ &video_h264_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_alaw_h261__vp8[] = {<br>+ &audio_alaw_no_name,<br>+ &video_h261_no_name,<br>+ &video_vp8_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_alaw_adec_h261__vp8[] = {<br>+ &audio_alaw_no_name,<br>+ &audio_declined_no_name,<br>+ &video_h261_no_name,<br>+ &video_vp8_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type with delete and add of streams */<br>+static const struct sdp_update_test mrg_by_type_09 = {<br>+ .initial = top_ulaw_alaw_h264,<br>+ .update_1 = top_alaw_h261__vp8,<br>+ .expected = top_alaw_adec_h261__vp8,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_ulaw_adec_h264[] = {<br>+ &audio_ulaw_no_name,<br>+ &audio_declined_no_name,<br>+ &video_h264_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type and adding streams */<br>+static const struct sdp_update_test mrg_by_type_10 = {<br>+ .initial = top_ulaw_adec_h264,<br>+ .update_1 = top_alaw_ulaw__vp8_h264,<br>+ .expected = top_alaw_ulaw__vp8_h264,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_adec_g722_h261[] = {<br>+ &audio_declined_no_name,<br>+ &audio_g722_no_name,<br>+ &video_h261_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by type and deleting old streams */<br>+static const struct sdp_update_test mrg_by_type_11 = {<br>+ .initial = top_ulaw_alaw_h264,<br>+ .update_1 = top_adec_g722_h261,<br>+ .expected = top_adec_g722_h261,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream audio_alaw4dave = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", "dave"<br>+};<br>+<br>+static const struct sdp_topology_stream audio_g7224dave = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", "dave"<br>+};<br>+<br>+static const struct sdp_topology_stream audio_ulaw4fred = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", "fred"<br>+};<br>+<br>+static const struct sdp_topology_stream audio_alaw4fred = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "alaw", "fred"<br>+};<br>+<br>+static const struct sdp_topology_stream audio_ulaw4rose = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "ulaw", "rose"<br>+};<br>+<br>+static const struct sdp_topology_stream audio_g7224rose = {<br>+ AST_MEDIA_TYPE_AUDIO, AST_STREAM_STATE_SENDRECV, "g722", "rose"<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream video_h2614dave = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", "dave"<br>+};<br>+<br>+static const struct sdp_topology_stream video_h2634dave = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", "dave"<br>+};<br>+<br>+static const struct sdp_topology_stream video_h2634fred = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h263", "fred"<br>+};<br>+<br>+static const struct sdp_topology_stream video_h2644fred = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", "fred"<br>+};<br>+<br>+static const struct sdp_topology_stream video_h2644rose = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h264", "rose"<br>+};<br>+<br>+static const struct sdp_topology_stream video_h2614rose = {<br>+ AST_MEDIA_TYPE_VIDEO, AST_STREAM_STATE_SENDRECV, "h261", "rose"<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_adave_alaw_afred_ulaw_arose_g722_vdave_h261_vfred_h263_vrose_h264[] = {<br>+ &audio_alaw4dave,<br>+ &audio_alaw_no_name,<br>+ &audio_ulaw4fred,<br>+ &audio_ulaw_no_name,<br>+ &audio_g7224rose,<br>+ &audio_g722_no_name,<br>+ &video_h2614dave,<br>+ &video_h261_no_name,<br>+ &video_h2634fred,<br>+ &video_h263_no_name,<br>+ &video_h2644rose,<br>+ &video_h264_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_vfred_vrose_vdave_h263_h264_h261_afred_ulaw_arose_g722_adave_alaw[] = {<br>+ &video_h2644fred,<br>+ &video_h2614rose,<br>+ &video_h2634dave,<br>+ &video_h263_no_name,<br>+ &video_h264_no_name,<br>+ &video_h261_no_name,<br>+ &audio_alaw4fred,<br>+ &audio_ulaw_no_name,<br>+ &audio_ulaw4rose,<br>+ &audio_g722_no_name,<br>+ &audio_g7224dave,<br>+ &audio_alaw_no_name,<br>+ NULL<br>+};<br>+<br>+static const struct sdp_topology_stream *top_adave_ulaw_afred_g722_arose_alaw_vdave_h263_vfred_h264_vrose_h261[] = {<br>+ &audio_g7224dave,<br>+ &audio_ulaw_no_name,<br>+ &audio_alaw4fred,<br>+ &audio_g722_no_name,<br>+ &audio_ulaw4rose,<br>+ &audio_alaw_no_name,<br>+ &video_h2634dave,<br>+ &video_h263_no_name,<br>+ &video_h2644fred,<br>+ &video_h264_no_name,<br>+ &video_h2614rose,<br>+ &video_h261_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by name and type with no new or deleted streams */<br>+static const struct sdp_update_test mrg_by_name_00 = {<br>+ .initial = top_adave_alaw_afred_ulaw_arose_g722_vdave_h261_vfred_h263_vrose_h264,<br>+ .update_1 = top_vfred_vrose_vdave_h263_h264_h261_afred_ulaw_arose_g722_adave_alaw,<br>+ .expected = top_adave_ulaw_afred_g722_arose_alaw_vdave_h263_vfred_h264_vrose_h261,<br>+};<br>+<br>+<br>+static const struct sdp_topology_stream *top_adave_g723_h261[] = {<br>+ &audio_g7224dave,<br>+ &audio_g723_no_name,<br>+ &video_h261_no_name,<br>+ NULL<br>+};<br>+<br>+/* Sorting by name and type adding names to streams */<br>+static const struct sdp_update_test mrg_by_name_01 = {<br>+ .initial = top_ulaw_alaw_h264,<br>+ .update_1 = top_adave_g723_h261,<br>+ .expected = top_adave_g723_h261,<br>+};<br>+<br>+<br>+/* Sorting by name and type removing names from streams */<br>+static const struct sdp_update_test mrg_by_name_02 = {<br>+ .initial = top_adave_g723_h261,<br>+ .update_1 = top_ulaw_alaw_h264,<br>+ .expected = top_ulaw_alaw_h264,<br>+};<br>+<br>+<br>+static const struct sdp_update_test *sdp_update_cases[] = {<br>+ /* Merging by type */<br>+ /* 00 */ &mrg_by_type_00,<br>+ /* 01 */ &mrg_by_type_01,<br>+ /* 02 */ &mrg_by_type_02,<br>+ /* 03 */ &mrg_by_type_03,<br>+ /* 04 */ &mrg_by_type_04,<br>+ /* 05 */ &mrg_by_type_05,<br>+ /* 06 */ &mrg_by_type_06,<br>+ /* 07 */ &mrg_by_type_07,<br>+ /* 08 */ &mrg_by_type_08,<br>+ /* 09 */ &mrg_by_type_09,<br>+ /* 10 */ &mrg_by_type_10,<br>+ /* 11 */ &mrg_by_type_11,<br>+<br>+ /* Merging by name and type */<br>+ /* 12 */ &mrg_by_name_00,<br>+ /* 13 */ &mrg_by_name_01,<br>+ /* 14 */ &mrg_by_name_02,<br>+};<br>+<br>+AST_TEST_DEFINE(sdp_update_topology)<br>+{<br>+ enum ast_test_result_state res;<br>+ unsigned int idx;<br>+ int status;<br>+ struct ast_sdp_options *options;<br>+ struct ast_stream_topology *topology;<br>+ struct ast_sdp_state *test_state = NULL;<br>+<br>+ static const struct sdp_format sdp_formats[] = {<br>+ { AST_MEDIA_TYPE_AUDIO, "ulaw,alaw,g722,g723" },<br>+ { AST_MEDIA_TYPE_VIDEO, "h261,h263,h264,vp8" },<br>+ { AST_MEDIA_TYPE_IMAGE, "t38" },<br>+ };<br>+<br>+ switch(cmd) {<br>+ case TEST_INIT:<br>+ info->name = "sdp_update_topology";<br>+ info->category = "/main/sdp/";<br>+ info->summary = "Merge topology updates from the system";<br>+ info->description =<br>+ "1) Create a SDP state with an optional initial topology.\n"<br>+ "2) Update the initial topology with one or two new topologies.\n"<br>+ "3) Get the SDP offer to merge the updates into the initial topology.\n"<br>+ "4) Check that the offered topology matches the expected topology.\n"<br>+ "5) Repeat these steps for each test case defined.";<br>+ return AST_TEST_NOT_RUN;<br>+ case TEST_EXECUTE:<br>+ break;<br>+ }<br>+<br>+ res = AST_TEST_FAIL;<br>+ for (idx = 0; idx < ARRAY_LEN(sdp_update_cases); ++idx) {<br>+ ast_test_status_update(test, "Starting update case %d\n", idx);<br>+<br>+ /* Create a SDP state with an optional initial topology. */<br>+ options = sdp_options_common();<br>+ if (!options) {<br>+ ast_test_status_update(test, "Failed to allocate SDP options\n");<br>+ goto end;<br>+ }<br>+ if (sdp_update_cases[idx]->max_streams) {<br>+ ast_sdp_options_set_max_streams(options, sdp_update_cases[idx]->max_streams);<br>+ }<br>+ if (build_sdp_option_formats(options, ARRAY_LEN(sdp_formats), sdp_formats)) {<br>+ ast_test_status_update(test, "Failed to setup SDP options new stream formats\n");<br>+ goto end;<br>+ }<br>+ if (sdp_update_cases[idx]->initial) {<br>+ topology = build_update_topology(sdp_update_cases[idx]->initial);<br>+ if (!topology) {<br>+ ast_test_status_update(test, "Failed to build initial SDP state topology\n");<br>+ goto end;<br>+ }<br>+ } else {<br>+ topology = NULL;<br>+ }<br>+ test_state = ast_sdp_state_alloc(topology, options);<br>+ ast_stream_topology_free(topology);<br>+ if (!test_state) {<br>+ ast_test_status_update(test, "Failed to build SDP state\n");<br>+ goto end;<br>+ }<br>+<br>+ /* Update the initial topology with one or two new topologies. */<br>+ topology = build_update_topology(sdp_update_cases[idx]->update_1);<br>+ if (!topology) {<br>+ ast_test_status_update(test, "Failed to build first update SDP state topology\n");<br>+ goto end;<br>+ }<br>+ status = ast_sdp_state_update_local_topology(test_state, topology);<br>+ ast_stream_topology_free(topology);<br>+ if (status) {<br>+ ast_test_status_update(test, "Failed to update first update SDP state topology\n");<br>+ goto end;<br>+ }<br>+ if (sdp_update_cases[idx]->update_2) {<br>+ topology = build_update_topology(sdp_update_cases[idx]->update_2);<br>+ if (!topology) {<br>+ ast_test_status_update(test, "Failed to build second update SDP state topology\n");<br>+ goto end;<br>+ }<br>+ status = ast_sdp_state_update_local_topology(test_state, topology);<br>+ ast_stream_topology_free(topology);<br>+ if (status) {<br>+ ast_test_status_update(test, "Failed to update second update SDP state topology\n");<br>+ goto end;<br>+ }<br>+ }<br>+<br>+ /* Get the SDP offer to merge the updates into the initial topology. */<br>+ if (!ast_sdp_state_get_local_sdp(test_state)) {<br>+ ast_test_status_update(test, "Failed to create offer SDP\n");<br>+ goto end;<br>+ }<br>+<br>+ /* Check that the offered topology matches the expected topology. */<br>+ topology = build_update_topology(sdp_update_cases[idx]->expected);<br>+ if (!topology) {<br>+ ast_test_status_update(test, "Failed to build expected topology\n");<br>+ goto end;<br>+ }<br>+ status = cmp_update_topology(test, topology,<br>+ ast_sdp_state_get_local_topology(test_state));<br>+ ast_stream_topology_free(topology);<br>+ if (status) {<br>+ ast_test_status_update(test, "Failed to match expected topology\n");<br>+ goto end;<br>+ }<br>+<br>+ /* Repeat for each test case defined. */<br>+ ast_sdp_state_free(test_state);<br>+ test_state = NULL;<br>+ }<br>+ res = AST_TEST_PASS;<br>+<br>+end:<br>+ ast_sdp_state_free(test_state);<br>+ return res;<br>+}<br>+<br> static int unload_module(void)<br> {<br> AST_TEST_UNREGISTER(invalid_rtpmap);<br>@@ -1186,10 +2014,13 @@<br> AST_TEST_UNREGISTER(find_attr);<br> AST_TEST_UNREGISTER(topology_to_sdp);<br> AST_TEST_UNREGISTER(sdp_to_topology);<br>- AST_TEST_UNREGISTER(sdp_merge_symmetric);<br>- AST_TEST_UNREGISTER(sdp_merge_crisscross);<br>- AST_TEST_UNREGISTER(sdp_merge_asymmetric);<br>+ AST_TEST_UNREGISTER(sdp_negotiation_initial);<br>+ AST_TEST_UNREGISTER(sdp_negotiation_type_change);<br>+ AST_TEST_UNREGISTER(sdp_negotiation_decline_incompatible);<br>+ AST_TEST_UNREGISTER(sdp_negotiation_decline_max_streams);<br>+ AST_TEST_UNREGISTER(sdp_negotiation_not_acceptable);<br> AST_TEST_UNREGISTER(sdp_ssrc_attributes);<br>+ AST_TEST_UNREGISTER(sdp_update_topology);<br> <br> return 0;<br> }<br>@@ -1201,10 +2032,13 @@<br> AST_TEST_REGISTER(find_attr);<br> AST_TEST_REGISTER(topology_to_sdp);<br> AST_TEST_REGISTER(sdp_to_topology);<br>- AST_TEST_REGISTER(sdp_merge_symmetric);<br>- AST_TEST_REGISTER(sdp_merge_crisscross);<br>- AST_TEST_REGISTER(sdp_merge_asymmetric);<br>+ AST_TEST_REGISTER(sdp_negotiation_initial);<br>+ AST_TEST_REGISTER(sdp_negotiation_type_change);<br>+ AST_TEST_REGISTER(sdp_negotiation_decline_incompatible);<br>+ AST_TEST_REGISTER(sdp_negotiation_decline_max_streams);<br>+ AST_TEST_REGISTER(sdp_negotiation_not_acceptable);<br> AST_TEST_REGISTER(sdp_ssrc_attributes);<br>+ AST_TEST_REGISTER(sdp_update_topology);<br> <br> return AST_MODULE_LOAD_SUCCESS;<br> }<br></pre><p>To view, visit <a href="https://gerrit.asterisk.org/5819">change 5819</a>. To unsubscribe, visit <a href="https://gerrit.asterisk.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.asterisk.org/5819"/><meta itemprop="name" content="View Change"/></div></div>
<div style="display:none"> Gerrit-Project: asterisk </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: If07fe6d79fbdce33968a9401d41d908385043a06 </div>
<div style="display:none"> Gerrit-Change-Number: 5819 </div>
<div style="display:none"> Gerrit-PatchSet: 5 </div>
<div style="display:none"> Gerrit-Owner: Richard Mudgett <rmudgett@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: George Joseph <gjoseph@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins2 </div>
<div style="display:none"> Gerrit-Reviewer: Joshua Colp <jcolp@digium.com> </div>
<div style="display:none"> Gerrit-Reviewer: Richard Mudgett <rmudgett@digium.com> </div>