webrtc: propagate more errors through the promise 73/262573/2
authorMatthew Waters <matthew@centricular.com>
Wed, 26 Aug 2020 05:45:35 +0000 (15:45 +1000)
committerSangchul Lee <sc11.lee@samsung.com>
Fri, 13 Aug 2021 09:10:48 +0000 (09:10 +0000)
Return errors on promises when things fail where available.

Things like parsing errors, invalid states, missing fields, unsupported
transitions, etc.

Change-Id: I1ca633d2620c99bf874a27d1c10f129d7aee96eb
Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/1565>
Signed-off-by: Sangchul Lee <sc11.lee@samsung.com>
ext/webrtc/gstwebrtcbin.c
ext/webrtc/utils.h
ext/webrtc/webrtcsdp.c
ext/webrtc/webrtcsdp.h
tests/check/elements/webrtcbin.c

index aeee8c1..c2ae103 100644 (file)
@@ -2493,7 +2493,8 @@ _add_data_channel_offer (GstWebRTCBin * webrtc, GstSDPMessage * msg,
 
 /* TODO: use the options argument */
 static GstSDPMessage *
-_create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
+_create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options,
+    GError ** error)
 {
   GstSDPMessage *ret;
   GString *bundled_mids = NULL;
@@ -2536,11 +2537,11 @@ _create_offer_task (GstWebRTCBin * webrtc, const GstStructure * options)
     guint bundle_media_index;
 
     reserved_pts = gather_reserved_pts (webrtc);
-    if (last_offer && _parse_bundle (last_offer, &last_bundle) && last_bundle
+    if (last_offer && _parse_bundle (last_offer, &last_bundle, NULL)
 #ifndef __TIZEN__
-        && last_bundle && last_bundle[0]
+        && last_bundle && last_bundle && last_bundle[0]
 #else
-        && last_bundle[0]
+        && last_bundle && last_bundle[0]
 #endif
         && _get_bundle_index (last_offer, last_bundle, &bundle_media_index)) {
       bundle_ufrag =
@@ -2817,7 +2818,8 @@ _get_rtx_target_pt_and_ssrc_from_caps (GstCaps * answer_caps, gint * target_pt,
 
 /* TODO: use the options argument */
 static GstSDPMessage *
-_create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
+_create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options,
+    GError ** error)
 {
   GstSDPMessage *ret = NULL;
   const GstWebRTCSessionDescription *pending_remote =
@@ -2832,12 +2834,13 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
   GstSDPMessage *last_answer = _get_latest_self_generated_sdp (webrtc);
 
   if (!webrtc->pending_remote_description) {
-    GST_ERROR_OBJECT (webrtc,
+    g_set_error_literal (error, GST_WEBRTC_BIN_ERROR,
+        GST_WEBRTC_BIN_ERROR_INVALID_STATE,
         "Asked to create an answer without a remote description");
     return NULL;
   }
 
-  if (!_parse_bundle (pending_remote->sdp, &bundled))
+  if (!_parse_bundle (pending_remote->sdp, &bundled, error))
     goto out;
 
   if (bundled) {
@@ -2845,8 +2848,8 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
     guint bundle_media_index;
 
     if (!_get_bundle_index (pending_remote->sdp, bundled, &bundle_idx)) {
-      GST_ERROR_OBJECT (webrtc, "Bundle tag is %s but no media found matching",
-          bundled[0]);
+      g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
+          "Bundle tag is %s but no media found matching", bundled[0]);
       goto out;
     }
 
@@ -2854,7 +2857,7 @@ _create_answer_task (GstWebRTCBin * webrtc, const GstStructure * options)
       bundled_mids = g_string_new ("BUNDLE");
     }
 
-    if (last_answer && _parse_bundle (last_answer, &last_bundle)
+    if (last_answer && _parse_bundle (last_answer, &last_bundle, NULL)
         && last_bundle && last_bundle[0]
         && _get_bundle_index (last_answer, last_bundle, &bundle_media_index)) {
       bundle_ufrag =
@@ -3241,14 +3244,15 @@ _create_sdp_task (GstWebRTCBin * webrtc, struct create_sdp *data)
   GstWebRTCSessionDescription *desc = NULL;
   GstSDPMessage *sdp = NULL;
   GstStructure *s = NULL;
+  GError *error = NULL;
 
   GST_INFO_OBJECT (webrtc, "creating %s sdp with options %" GST_PTR_FORMAT,
       gst_webrtc_sdp_type_to_string (data->type), data->options);
 
   if (data->type == GST_WEBRTC_SDP_TYPE_OFFER)
-    sdp = _create_offer_task (webrtc, data->options);
+    sdp = _create_offer_task (webrtc, data->options, &error);
   else if (data->type == GST_WEBRTC_SDP_TYPE_ANSWER)
-    sdp = _create_answer_task (webrtc, data->options);
+    sdp = _create_answer_task (webrtc, data->options, &error);
   else {
     g_assert_not_reached ();
     goto out;
@@ -3259,6 +3263,13 @@ _create_sdp_task (GstWebRTCBin * webrtc, struct create_sdp *data)
     s = gst_structure_new ("application/x-gst-promise",
         gst_webrtc_sdp_type_to_string (data->type),
         GST_TYPE_WEBRTC_SESSION_DESCRIPTION, desc, NULL);
+  } else {
+    g_warn_if_fail (error != NULL);
+    GST_WARNING_OBJECT (webrtc, "returning error: %s",
+        error ? error->message : "Unknown");
+    s = gst_structure_new ("application/x-gstwebrtcbin-error",
+        "error", G_TYPE_ERROR, error, NULL);
+    g_clear_error (&error);
   }
 
 out:
@@ -3714,7 +3725,7 @@ static void
 _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
     const GstSDPMessage * sdp, guint media_idx,
     TransportStream * stream, GstWebRTCRTPTransceiver * rtp_trans,
-    GStrv bundled, guint bundle_idx)
+    GStrv bundled, guint bundle_idx, GError ** error)
 {
   WebRTCTransceiver *trans = WEBRTC_TRANSCEIVER (rtp_trans);
   GstWebRTCRTPTransceiverDirection prev_dir = rtp_trans->current_direction;
@@ -3751,20 +3762,28 @@ _update_transceiver_from_sdp_media (GstWebRTCBin * webrtc,
     local_setup = _get_dtls_setup_from_media (local_media);
     remote_setup = _get_dtls_setup_from_media (remote_media);
     new_setup = _get_final_setup (local_setup, remote_setup);
-    if (new_setup == GST_WEBRTC_DTLS_SETUP_NONE)
+    if (new_setup == GST_WEBRTC_DTLS_SETUP_NONE) {
+      g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
+          "Cannot intersect direction attributes for media %u", media_idx);
       return;
+    }
 
     local_dir = _get_direction_from_media (local_media);
     remote_dir = _get_direction_from_media (remote_media);
     new_dir = _get_final_direction (local_dir, remote_dir);
-
-    if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE)
+    if (new_dir == GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
+      g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
+          "Cannot intersect dtls setup attributes for media %u", media_idx);
       return;
+    }
 
     if (prev_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE
         && new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE
         && prev_dir != new_dir) {
-      GST_FIXME_OBJECT (webrtc, "implement transceiver direction changes");
+      g_set_error (error, GST_WEBRTC_BIN_ERROR,
+          GST_WEBRTC_BIN_ERROR_NOT_IMPLEMENTED,
+          "transceiver direction changes are not implemented. Media %u",
+          media_idx);
       return;
     }
 
@@ -3958,7 +3977,8 @@ _generate_data_channel_id (GstWebRTCBin * webrtc)
 
 static void
 _update_data_channel_from_sdp_media (GstWebRTCBin * webrtc,
-    const GstSDPMessage * sdp, guint media_idx, TransportStream * stream)
+    const GstSDPMessage * sdp, guint media_idx, TransportStream * stream,
+    GError ** error)
 {
   const GstSDPMedia *local_media, *remote_media;
   GstWebRTCDTLSSetup local_setup, remote_setup, new_setup;
@@ -3977,8 +3997,11 @@ _update_data_channel_from_sdp_media (GstWebRTCBin * webrtc,
   local_setup = _get_dtls_setup_from_media (local_media);
   remote_setup = _get_dtls_setup_from_media (remote_media);
   new_setup = _get_final_setup (local_setup, remote_setup);
-  if (new_setup == GST_WEBRTC_DTLS_SETUP_NONE)
+  if (new_setup == GST_WEBRTC_DTLS_SETUP_NONE) {
+    g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
+        "Cannot intersect dtls setup for media %u", media_idx);
     return;
+  }
 
   /* data channel is always rtcp-muxed to avoid generating ICE candidates
    * for RTCP */
@@ -3987,8 +4010,12 @@ _update_data_channel_from_sdp_media (GstWebRTCBin * webrtc,
 
   local_port = _get_sctp_port_from_media (local_media);
   remote_port = _get_sctp_port_from_media (local_media);
-  if (local_port == -1 || remote_port == -1)
+  if (local_port == -1 || remote_port == -1) {
+    g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
+        "Could not find sctp port for media %u (local %i, remote %i)",
+        media_idx, local_port, remote_port);
     return;
+  }
 
   if (0 == (local_max_size =
           _get_sctp_max_message_size_from_media (local_media)))
@@ -4101,7 +4128,7 @@ done:
 
 static gboolean
 _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
-    GstWebRTCSessionDescription * sdp)
+    GstWebRTCSessionDescription * sdp, GError ** error)
 {
   int i;
   gboolean ret = FALSE;
@@ -4112,14 +4139,14 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
   /* FIXME: With some peers, it's possible we could have
    * multiple bundles to deal with, although I've never seen one yet */
   if (webrtc->bundle_policy != GST_WEBRTC_BUNDLE_POLICY_NONE)
-    if (!_parse_bundle (sdp->sdp, &bundled))
+    if (!_parse_bundle (sdp->sdp, &bundled, error))
       goto done;
 
   if (bundled) {
 
     if (!_get_bundle_index (sdp->sdp, bundled, &bundle_idx)) {
-      GST_ERROR_OBJECT (webrtc, "Bundle tag is %s but no media found matching",
-          bundled[0]);
+      g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
+          "Bundle tag is %s but no media found matching", bundled[0]);
       goto done;
     }
 
@@ -4170,7 +4197,8 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
       webrtc_transceiver_set_transport ((WebRTCTransceiver *) trans, stream);
 
     if (source == SDP_LOCAL && sdp->type == GST_WEBRTC_SDP_TYPE_OFFER && !trans) {
-      GST_ERROR ("State mismatch.  Could not find local transceiver by mline.");
+      g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
+          "State mismatch.  Could not find local transceiver by mline %u", i);
       goto done;
     } else {
       if (g_strcmp0 (gst_sdp_media_get_media (media), "audio") == 0 ||
@@ -4193,9 +4221,10 @@ _update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
         }
 
         _update_transceiver_from_sdp_media (webrtc, sdp->sdp, i, stream,
-            trans, bundled, bundle_idx);
+            trans, bundled, bundle_idx, error);
       } else if (_message_media_is_datachannel (sdp->sdp, i)) {
-        _update_data_channel_from_sdp_media (webrtc, sdp->sdp, i, stream);
+        _update_data_channel_from_sdp_media (webrtc, sdp->sdp, i, stream,
+            error);
       } else {
         GST_ERROR_OBJECT (webrtc, "Unknown media type in SDP at index %u", i);
       }
@@ -4219,6 +4248,20 @@ done:
   return ret;
 }
 
+static gboolean
+check_transceivers_not_removed (GstWebRTCBin * webrtc,
+    GstWebRTCSessionDescription * previous, GstWebRTCSessionDescription * new)
+{
+  if (!previous)
+    return TRUE;
+
+  if (gst_sdp_message_medias_len (previous->sdp) >
+      gst_sdp_message_medias_len (new->sdp))
+    return FALSE;
+
+  return TRUE;
+}
+
 struct set_description
 {
   GstPromise *promise;
@@ -4226,6 +4269,30 @@ struct set_description
   GstWebRTCSessionDescription *sdp;
 };
 
+static GstWebRTCSessionDescription *
+get_previous_description (GstWebRTCBin * webrtc, SDPSource source,
+    GstWebRTCSDPType type)
+{
+  switch (type) {
+    case GST_WEBRTC_SDP_TYPE_OFFER:
+    case GST_WEBRTC_SDP_TYPE_PRANSWER:
+    case GST_WEBRTC_SDP_TYPE_ANSWER:
+      if (source == SDP_LOCAL) {
+        return webrtc->current_local_description;
+      } else {
+        return webrtc->current_remote_description;
+      }
+    case GST_WEBRTC_SDP_TYPE_ROLLBACK:
+      return NULL;
+    default:
+      /* other values mean memory corruption/uninitialized! */
+      g_assert_not_reached ();
+      break;
+  }
+
+  return NULL;
+}
+
 /* http://w3c.github.io/webrtc-pc/#set-description */
 static void
 _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
@@ -4251,29 +4318,31 @@ _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
     g_free (type_str);
   }
 
-  if (!validate_sdp (webrtc->signaling_state, sd->source, sd->sdp, &error)) {
-    GST_ERROR_OBJECT (webrtc, "%s", error->message);
-    g_clear_error (&error);
-    goto out;
-  }
-
-  if (webrtc->priv->is_closed) {
-    GST_WARNING_OBJECT (webrtc, "we are closed");
+  if (!validate_sdp (webrtc->signaling_state, sd->source, sd->sdp, &error))
     goto out;
-  }
 
   if (webrtc->bundle_policy != GST_WEBRTC_BUNDLE_POLICY_NONE)
-    if (!_parse_bundle (sd->sdp->sdp, &bundled))
+    if (!_parse_bundle (sd->sdp->sdp, &bundled, &error))
       goto out;
 
   if (bundled) {
     if (!_get_bundle_index (sd->sdp->sdp, bundled, &bundle_idx)) {
-      GST_ERROR_OBJECT (webrtc, "Bundle tag is %s but no media found matching",
-          bundled[0]);
+      g_set_error (&error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
+          "Bundle tag is %s but no matching media found", bundled[0]);
       goto out;
     }
   }
 
+  if (!check_transceivers_not_removed (webrtc,
+          get_previous_description (webrtc, sd->source, sd->sdp->type),
+          sd->sdp)) {
+    g_set_error_literal (&error, GST_WEBRTC_BIN_ERROR,
+        GST_WEBRTC_BIN_ERROR_BAD_SDP,
+        "m=lines removed from the SDP. Processing a completely new connection "
+        "is not currently supported.");
+    goto out;
+  }
+
   switch (sd->sdp->type) {
     case GST_WEBRTC_SDP_TYPE_OFFER:{
       if (sd->source == SDP_LOCAL) {
@@ -4419,7 +4488,8 @@ _set_description_task (GstWebRTCBin * webrtc, struct set_description *sd)
     GList *tmp;
 
     /* media modifications */
-    _update_transceivers_from_sdp (webrtc, sd->source, sd->sdp);
+    if (!_update_transceivers_from_sdp (webrtc, sd->source, sd->sdp, &error))
+      goto out;
 
     for (tmp = webrtc->priv->pending_sink_transceivers; tmp;) {
       GstWebRTCBinPad *pad = GST_WEBRTC_BIN_PAD (tmp->data);
@@ -4599,7 +4669,15 @@ out:
   g_strfreev (bundled);
 
   PC_UNLOCK (webrtc);
-  gst_promise_reply (sd->promise, NULL);
+  if (error) {
+    GST_WARNING_OBJECT (webrtc, "returning error: %s", error->message);
+    gst_promise_reply (sd->promise,
+        gst_structure_new ("application/x-getwebrtcbin-error", "error",
+            G_TYPE_ERROR, error, NULL));
+    g_clear_error (&error);
+  } else {
+    gst_promise_reply (sd->promise, NULL);
+  }
   PC_LOCK (webrtc);
 }
 
index ab4d58e..ac18673 100644 (file)
@@ -40,6 +40,7 @@ typedef enum
   GST_WEBRTC_BIN_ERROR_SCTP_FAILURE,
   GST_WEBRTC_BIN_ERROR_DATA_CHANNEL_FAILURE,
   GST_WEBRTC_BIN_ERROR_CLOSED,
+  GST_WEBRTC_BIN_ERROR_NOT_IMPLEMENTED,
 } GstWebRTCError;
 
 GstPadTemplate *        _find_pad_template          (GstElement * element,
index 8164b7a..4131a35 100644 (file)
@@ -875,7 +875,7 @@ _get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
 }
 
 gboolean
-_parse_bundle (GstSDPMessage * sdp, GStrv * bundled)
+_parse_bundle (GstSDPMessage * sdp, GStrv * bundled, GError ** error)
 {
   const gchar *group;
   gboolean ret = FALSE;
@@ -886,8 +886,9 @@ _parse_bundle (GstSDPMessage * sdp, GStrv * bundled)
     *bundled = g_strsplit (group + strlen ("BUNDLE "), " ", 0);
 
     if (!(*bundled)[0]) {
-      GST_ERROR ("Invalid format for BUNDLE group, expected at least "
-          "one mid (%s)", group);
+      g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
+          "Invalid format for BUNDLE group, expected at least one mid (%s)",
+          group);
       g_strfreev (*bundled);
       *bundled = NULL;
       goto done;
index 1501cbc..c55709b 100644 (file)
@@ -101,7 +101,8 @@ gboolean                            _get_bundle_index                       (Gst
                                                                              guint * idx);
 G_GNUC_INTERNAL
 gboolean                            _parse_bundle                           (GstSDPMessage * sdp,
-                                                                             GStrv * bundled);
+                                                                             GStrv * bundled,
+                                                                             GError ** error);
 
 G_GNUC_INTERNAL
 const gchar *                       _media_get_ice_pwd                  (const GstSDPMessage * msg,
index 3b79bed..89a141c 100644 (file)
@@ -177,14 +177,19 @@ _on_answer_received (GstPromise * promise, gpointer user_data)
   GstElement *answerer = TEST_GET_ANSWERER (t);
   const GstStructure *reply;
   GstWebRTCSessionDescription *answer = NULL;
-  gchar *desc;
+  GError *error = NULL;
 
   reply = gst_promise_get_reply (promise);
-  gst_structure_get (reply, "answer",
-      GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL);
-  desc = gst_sdp_message_as_text (answer->sdp);
-  GST_INFO ("Created Answer: %s", desc);
-  g_free (desc);
+  if (gst_structure_get (reply, "answer",
+          GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &answer, NULL)) {
+    gchar *desc = gst_sdp_message_as_text (answer->sdp);
+    GST_INFO ("Created Answer: %s", desc);
+    g_free (desc);
+  } else if (gst_structure_get (reply, "error", G_TYPE_ERROR, &error, NULL)) {
+    GST_INFO ("Creating answer resulted in error: %s", error->message);
+  } else {
+    g_assert_not_reached ();
+  }
 
   g_mutex_lock (&t->lock);
 
@@ -196,15 +201,28 @@ _on_answer_received (GstPromise * promise, gpointer user_data)
   }
   gst_promise_unref (promise);
 
-  promise = gst_promise_new_with_change_func (_on_answer_set, t, NULL);
-  g_signal_emit_by_name (answerer, "set-local-description", t->answer_desc,
-      promise);
-  promise = gst_promise_new_with_change_func (_on_answer_set, t, NULL);
-  g_signal_emit_by_name (offeror, "set-remote-description", t->answer_desc,
-      promise);
+  if (error)
+    goto error;
+
+  if (t->answer_desc) {
+    promise = gst_promise_new_with_change_func (_on_answer_set, t, NULL);
+    g_signal_emit_by_name (answerer, "set-local-description", t->answer_desc,
+        promise);
+    promise = gst_promise_new_with_change_func (_on_answer_set, t, NULL);
+    g_signal_emit_by_name (offeror, "set-remote-description", t->answer_desc,
+        promise);
+  }
 
   test_webrtc_signal_state_unlocked (t, STATE_ANSWER_CREATED);
   g_mutex_unlock (&t->lock);
+  return;
+
+error:
+  g_clear_error (&error);
+  if (t->state < STATE_ERROR)
+    test_webrtc_signal_state_unlocked (t, STATE_ERROR);
+  g_mutex_unlock (&t->lock);
+  return;
 }
 
 static void
@@ -232,14 +250,19 @@ _on_offer_received (GstPromise * promise, gpointer user_data)
   GstElement *answerer = TEST_GET_ANSWERER (t);
   const GstStructure *reply;
   GstWebRTCSessionDescription *offer = NULL;
-  gchar *desc;
+  GError *error = NULL;
 
   reply = gst_promise_get_reply (promise);
-  gst_structure_get (reply, "offer",
-      GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL);
-  desc = gst_sdp_message_as_text (offer->sdp);
-  GST_INFO ("Created offer: %s", desc);
-  g_free (desc);
+  if (gst_structure_get (reply, "offer",
+          GST_TYPE_WEBRTC_SESSION_DESCRIPTION, &offer, NULL)) {
+    gchar *desc = gst_sdp_message_as_text (offer->sdp);
+    GST_INFO ("Created offer: %s", desc);
+    g_free (desc);
+  } else if (gst_structure_get (reply, "error", G_TYPE_ERROR, &error, NULL)) {
+    GST_INFO ("Creating offer resulted in error: %s", error->message);
+  } else {
+    g_assert_not_reached ();
+  }
 
   g_mutex_lock (&t->lock);
 
@@ -251,18 +274,31 @@ _on_offer_received (GstPromise * promise, gpointer user_data)
   }
   gst_promise_unref (promise);
 
-  promise = gst_promise_new_with_change_func (_on_offer_set, t, NULL);
-  g_signal_emit_by_name (offeror, "set-local-description", t->offer_desc,
-      promise);
-  promise = gst_promise_new_with_change_func (_on_offer_set, t, NULL);
-  g_signal_emit_by_name (answerer, "set-remote-description", t->offer_desc,
-      promise);
+  if (error)
+    goto error;
 
-  promise = gst_promise_new_with_change_func (_on_answer_received, t, NULL);
-  g_signal_emit_by_name (answerer, "create-answer", NULL, promise);
+  if (t->offer_desc) {
+    promise = gst_promise_new_with_change_func (_on_offer_set, t, NULL);
+    g_signal_emit_by_name (offeror, "set-local-description", t->offer_desc,
+        promise);
+    promise = gst_promise_new_with_change_func (_on_offer_set, t, NULL);
+    g_signal_emit_by_name (answerer, "set-remote-description", t->offer_desc,
+        promise);
+
+    promise = gst_promise_new_with_change_func (_on_answer_received, t, NULL);
+    g_signal_emit_by_name (answerer, "create-answer", NULL, promise);
+  }
 
   test_webrtc_signal_state_unlocked (t, STATE_OFFER_CREATED);
   g_mutex_unlock (&t->lock);
+  return;
+
+error:
+  g_clear_error (&error);
+  if (t->state < STATE_ERROR)
+    test_webrtc_signal_state_unlocked (t, STATE_ERROR);
+  g_mutex_unlock (&t->lock);
+  return;
 }
 
 static gboolean
@@ -2053,7 +2089,7 @@ _check_bundle_tag (struct test_webrtc *t, GstElement * element,
   GStrv expected = user_data;
   guint i;
 
-  fail_unless (_parse_bundle (sd->sdp, &bundled));
+  fail_unless (_parse_bundle (sd->sdp, &bundled, NULL));
 
   if (!bundled) {
     fail_unless_equals_int (g_strv_length (expected), 0);
@@ -2745,6 +2781,98 @@ GST_START_TEST (test_renego_transceiver_set_direction)
 
 GST_END_TEST;
 
+static void
+offer_remove_last_media (struct test_webrtc *t, GstElement * element,
+    GstPromise * promise, gpointer user_data)
+{
+  guint i, n;
+  GstSDPMessage *new, *old;
+  const GstSDPOrigin *origin;
+  const GstSDPConnection *conn;
+
+  old = t->offer_desc->sdp;
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_message_new (&new));
+
+  origin = gst_sdp_message_get_origin (old);
+  conn = gst_sdp_message_get_connection (old);
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_message_set_version (new,
+          gst_sdp_message_get_version (old)));
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_message_set_origin (new,
+          origin->username, origin->sess_id, origin->sess_version,
+          origin->nettype, origin->addrtype, origin->addr));
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_message_set_session_name (new,
+          gst_sdp_message_get_session_name (old)));
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_message_set_information (new,
+          gst_sdp_message_get_information (old)));
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_message_set_uri (new,
+          gst_sdp_message_get_uri (old)));
+  fail_unless_equals_int (GST_SDP_OK, gst_sdp_message_set_connection (new,
+          conn->nettype, conn->addrtype, conn->address, conn->ttl,
+          conn->addr_number));
+
+  n = gst_sdp_message_attributes_len (old);
+  for (i = 0; i < n; i++) {
+    const GstSDPAttribute *a = gst_sdp_message_get_attribute (old, i);
+    fail_unless_equals_int (GST_SDP_OK, gst_sdp_message_add_attribute (new,
+            a->key, a->value));
+  }
+
+  n = gst_sdp_message_medias_len (old);
+  fail_unless (n > 0);
+  for (i = 0; i < n - 1; i++) {
+    const GstSDPMedia *m = gst_sdp_message_get_media (old, i);
+    GstSDPMedia *new_m;
+
+    fail_unless_equals_int (GST_SDP_OK, gst_sdp_media_copy (m, &new_m));
+    fail_unless_equals_int (GST_SDP_OK, gst_sdp_message_add_media (new, new_m));
+    gst_sdp_media_init (new_m);
+    gst_sdp_media_free (new_m);
+  }
+
+  gst_webrtc_session_description_free (t->offer_desc);
+  t->offer_desc = gst_webrtc_session_description_new (GST_WEBRTC_SDP_TYPE_OFFER,
+      new);
+}
+
+static void
+offer_set_produced_error (struct test_webrtc *t, GstElement * element,
+    GstPromise * promise, gpointer user_data)
+{
+  const GstStructure *reply;
+  GError *error = NULL;
+
+  reply = gst_promise_get_reply (promise);
+  fail_unless (gst_structure_get (reply, "error", G_TYPE_ERROR, &error, NULL));
+  GST_INFO ("error produced: %s", error->message);
+  g_clear_error (&error);
+
+  test_webrtc_signal_state_unlocked (t, STATE_CUSTOM);
+}
+
+GST_START_TEST (test_renego_lose_media_fails)
+{
+  struct test_webrtc *t = create_audio_video_test ();
+  VAL_SDP_INIT (offer, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
+  VAL_SDP_INIT (answer, _count_num_sdp_media, GUINT_TO_POINTER (2), NULL);
+
+  /* check that removing an m=line will produce an error */
+
+  test_validate_sdp (t, &offer, &answer);
+
+  test_webrtc_reset_negotiation (t);
+
+  t->on_offer_created = offer_remove_last_media;
+  t->on_offer_set = offer_set_produced_error;
+  t->on_answer_created = NULL;
+
+  test_webrtc_create_offer (t, t->webrtc1);
+  test_webrtc_wait_for_state_mask (t, 1 << STATE_CUSTOM);
+
+  test_webrtc_free (t);
+}
+
+GST_END_TEST;
+
 static Suite *
 webrtcbin_suite (void)
 {
@@ -2785,6 +2913,7 @@ webrtcbin_suite (void)
     tcase_add_test (tc, test_bundle_renego_add_stream);
     tcase_add_test (tc, test_bundle_max_compat_max_bundle_renego_add_stream);
     tcase_add_test (tc, test_renego_transceiver_set_direction);
+    tcase_add_test (tc, test_renego_lose_media_fails);
     if (sctpenc && sctpdec) {
       tcase_add_test (tc, test_data_channel_create);
       tcase_add_test (tc, test_data_channel_remote_notify);