/* 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;
guint bundle_media_index;
reserved_pts = gather_reserved_pts (webrtc);
- if (last_offer && _parse_bundle (last_offer, &last_bundle) && last_bundle
- && last_bundle && last_bundle[0]
+ if (last_offer && _parse_bundle (last_offer, &last_bundle, NULL)
+ && last_bundle && last_bundle && last_bundle[0]
&& _get_bundle_index (last_offer, last_bundle, &bundle_media_index)) {
bundle_ufrag =
g_strdup (_media_get_ice_ufrag (last_offer, bundle_media_index));
/* 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 =
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) {
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;
}
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 =
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;
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:
_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;
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;
}
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;
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 */
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)))
static gboolean
_update_transceivers_from_sdp (GstWebRTCBin * webrtc, SDPSource source,
- GstWebRTCSessionDescription * sdp)
+ GstWebRTCSessionDescription * sdp, GError ** error)
{
int i;
gboolean ret = FALSE;
/* 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;
}
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 ||
}
_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);
}
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;
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)
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);
+ if (!validate_sdp (webrtc->signaling_state, sd->source, sd->sdp, &error))
goto out;
- }
-
- if (webrtc->priv->is_closed) {
- GST_WARNING_OBJECT (webrtc, "we are closed");
- 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) {
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);
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);
}
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);
}
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
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);
}
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
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);
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)
{
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);