<p>Richard Mudgett has uploaded this change for <strong>review</strong>.</p><p><a href="https://gerrit.asterisk.org/5819">View Change</a></p><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,399 insertions(+), 612 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;">git pull ssh://gerrit.asterisk.org:29418/asterisk refs/changes/19/5819/1</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 d31a0de..1f63edc 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(&current_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(&current_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(&current_vect); current_idx--;) {<br>+             idx = AST_VECTOR_GET(&current_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(&current_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>+            &current_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>+            &current_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(&current_vect); current_idx--;) {<br>+             idx = AST_VECTOR_GET(&current_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(&current_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(&current_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(&current->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 b617744..1130bc9 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..99cdb48 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.\n";<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,725 @@<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>+};<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 +2013,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 +2031,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: newchange </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: 1 </div>
<div style="display:none"> Gerrit-Owner: Richard Mudgett <rmudgett@digium.com> </div>