2 * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Library General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Library General Public License for more details.
14 * You should have received a copy of the GNU Library General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
24 #include "webrtcsdp.h"
31 #define IS_EMPTY_SDP_ATTRIBUTE(val) (val == NULL || g_strcmp0(val, "") == 0)
34 _sdp_source_to_string (SDPSource source)
47 _check_valid_state_for_sdp_change (GstWebRTCSignalingState state,
48 SDPSource source, GstWebRTCSDPType type, GError ** error)
50 #define STATE(val) GST_WEBRTC_SIGNALING_STATE_ ## val
51 #define TYPE(val) GST_WEBRTC_SDP_TYPE_ ## val
53 if (source == SDP_LOCAL && type == TYPE (OFFER) && state == STATE (STABLE))
55 if (source == SDP_LOCAL && type == TYPE (OFFER)
56 && state == STATE (HAVE_LOCAL_OFFER))
58 if (source == SDP_LOCAL && type == TYPE (ANSWER)
59 && state == STATE (HAVE_REMOTE_OFFER))
61 if (source == SDP_LOCAL && type == TYPE (PRANSWER)
62 && state == STATE (HAVE_REMOTE_OFFER))
64 if (source == SDP_LOCAL && type == TYPE (PRANSWER)
65 && state == STATE (HAVE_LOCAL_PRANSWER))
68 if (source == SDP_REMOTE && type == TYPE (OFFER) && state == STATE (STABLE))
70 if (source == SDP_REMOTE && type == TYPE (OFFER)
71 && state == STATE (HAVE_REMOTE_OFFER))
73 if (source == SDP_REMOTE && type == TYPE (ANSWER)
74 && state == STATE (HAVE_LOCAL_OFFER))
76 if (source == SDP_REMOTE && type == TYPE (PRANSWER)
77 && state == STATE (HAVE_LOCAL_OFFER))
79 if (source == SDP_REMOTE && type == TYPE (PRANSWER)
80 && state == STATE (HAVE_REMOTE_PRANSWER))
84 const gchar *state_str =
85 _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
87 const gchar *type_str =
88 _enum_value_to_string (GST_TYPE_WEBRTC_SDP_TYPE, type);
89 g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_INVALID_STATE,
90 "Not in the correct state (%s) for setting %s %s description",
91 state_str, _sdp_source_to_string (source), type_str);
101 _check_sdp_crypto (SDPSource source, GstWebRTCSessionDescription * sdp,
104 const gchar *message_fingerprint;
105 const GstSDPKey *key;
108 key = gst_sdp_message_get_key (sdp->sdp);
109 if (!IS_EMPTY_SDP_ATTRIBUTE (key->data)) {
110 g_set_error_literal (error, GST_WEBRTC_ERROR,
111 GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR, "sdp contains a k line");
115 message_fingerprint =
116 gst_sdp_message_get_attribute_val (sdp->sdp, "fingerprint");
117 for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
118 const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i);
119 const gchar *media_fingerprint =
120 gst_sdp_media_get_attribute_val (media, "fingerprint");
122 if (!IS_EMPTY_SDP_ATTRIBUTE (message_fingerprint)
123 && !IS_EMPTY_SDP_ATTRIBUTE (media_fingerprint)) {
124 g_set_error (error, GST_WEBRTC_ERROR,
125 GST_WEBRTC_ERROR_FINGERPRINT_FAILURE,
126 "No fingerprint lines in sdp for media %u", i);
135 _message_has_attribute_key (const GstSDPMessage * msg, const gchar * key)
138 for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
139 const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
141 if (g_strcmp0 (attr->key, key) == 0)
150 _session_has_attribute_key_value (const GstSDPMessage * msg, const gchar * key,
154 for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
155 const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
157 if (g_strcmp0 (attr->key, key) == 0 && g_strcmp0 (attr->value, value) == 0)
165 _check_trickle_ice (GstSDPMessage * msg, GError ** error)
167 if (!_session_has_attribute_key_value (msg, "ice-options", "trickle")) {
168 g_set_error_literal (error, GST_WEBRTC_ERROR,
169 GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
170 "No required \'a=ice-options:trickle\' line in sdp");
176 _media_has_attribute_key (const GstSDPMedia * media, const gchar * key)
179 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
180 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
182 if (g_strcmp0 (attr->key, key) == 0)
190 _media_has_mid (const GstSDPMedia * media, guint media_idx, GError ** error)
192 const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
193 if (IS_EMPTY_SDP_ATTRIBUTE (mid)) {
194 g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
195 "media %u is missing or contains an empty \'mid\' attribute",
203 _media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
205 const gchar *ice_ufrag;
207 ice_ufrag = gst_sdp_message_get_attribute_val (msg, "ice-ufrag");
208 if (IS_EMPTY_SDP_ATTRIBUTE (ice_ufrag)) {
209 const GstSDPMedia *media = gst_sdp_message_get_media (msg, media_idx);
210 ice_ufrag = gst_sdp_media_get_attribute_val (media, "ice-ufrag");
211 if (IS_EMPTY_SDP_ATTRIBUTE (ice_ufrag))
218 _media_get_ice_pwd (const GstSDPMessage * msg, guint media_idx)
220 const gchar *ice_pwd;
222 ice_pwd = gst_sdp_message_get_attribute_val (msg, "ice-pwd");
223 if (IS_EMPTY_SDP_ATTRIBUTE (ice_pwd)) {
224 const GstSDPMedia *media = gst_sdp_message_get_media (msg, media_idx);
225 ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
226 if (IS_EMPTY_SDP_ATTRIBUTE (ice_pwd))
233 _media_has_setup (const GstSDPMedia * media, guint media_idx, GError ** error)
235 static const gchar *valid_setups[] = { "actpass", "active", "passive", NULL };
236 const gchar *setup = gst_sdp_media_get_attribute_val (media, "setup");
237 if (IS_EMPTY_SDP_ATTRIBUTE (setup)) {
238 g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
239 "media %u is missing or contains an empty \'setup\' attribute",
243 if (!g_strv_contains (valid_setups, setup)) {
244 g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
245 "media %u contains unknown \'setup\' attribute, \'%s\'", media_idx,
254 _media_has_dtls_id (const GstSDPMedia * media, guint media_idx, GError ** error)
256 const gchar *dtls_id = gst_sdp_media_get_attribute_val (media, "ice-pwd");
257 if (IS_EMPTY_SDP_ATTRIBUTE (dtls_id)) {
258 g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
259 "media %u is missing or contains an empty \'dtls-id\' attribute",
267 validate_sdp (GstWebRTCSignalingState state, SDPSource source,
268 GstWebRTCSessionDescription * sdp, GError ** error)
270 const gchar *group, *bundle_ice_ufrag = NULL, *bundle_ice_pwd = NULL;
271 gchar **group_members = NULL;
272 gboolean is_bundle = FALSE;
275 if (!_check_valid_state_for_sdp_change (state, source, sdp->type, error))
277 if (!_check_sdp_crypto (source, sdp, error))
279 /* not explicitly required
280 if (ICE && !_check_trickle_ice (sdp->sdp))
282 group = gst_sdp_message_get_attribute_val (sdp->sdp, "group");
283 is_bundle = group && g_str_has_prefix (group, "BUNDLE");
285 group_members = g_strsplit (&group[6], " ", -1);
287 for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
288 const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i);
290 gboolean media_in_bundle = FALSE;
291 if (!_media_has_mid (media, i, error))
293 mid = gst_sdp_media_get_attribute_val (media, "mid");
294 media_in_bundle = is_bundle
295 && g_strv_contains ((const gchar **) group_members, mid);
296 if (!_media_get_ice_ufrag (sdp->sdp, i)) {
297 g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
298 "media %u is missing or contains an empty \'ice-ufrag\' attribute",
302 if (!_media_get_ice_pwd (sdp->sdp, i)) {
303 g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
304 "media %u is missing or contains an empty \'ice-pwd\' attribute", i);
307 if (!_media_has_setup (media, i, error))
309 /* check parameters in bundle are the same */
310 if (media_in_bundle) {
311 const gchar *ice_ufrag =
312 gst_sdp_media_get_attribute_val (media, "ice-ufrag");
313 const gchar *ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
314 if (!bundle_ice_ufrag)
315 bundle_ice_ufrag = ice_ufrag;
316 else if (g_strcmp0 (bundle_ice_ufrag, ice_ufrag) != 0) {
317 g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
318 "media %u has different ice-ufrag values in bundle. "
319 "%s != %s", i, bundle_ice_ufrag, ice_ufrag);
322 if (!bundle_ice_pwd) {
323 bundle_ice_pwd = ice_pwd;
324 } else if (g_strcmp0 (bundle_ice_pwd, ice_pwd) != 0) {
325 g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
326 "media %u has different ice-pwd values in bundle. "
327 "%s != %s", i, bundle_ice_pwd, ice_pwd);
333 g_strfreev (group_members);
338 g_strfreev (group_members);
342 GstWebRTCRTPTransceiverDirection
343 _get_direction_from_media (const GstSDPMedia * media)
345 GstWebRTCRTPTransceiverDirection new_dir =
346 GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
349 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
350 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
352 if (g_strcmp0 (attr->key, "sendonly") == 0) {
353 if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
354 GST_ERROR ("Multiple direction attributes");
355 return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
357 new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
358 } else if (g_strcmp0 (attr->key, "sendrecv") == 0) {
359 if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
360 GST_ERROR ("Multiple direction attributes");
361 return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
363 new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV;
364 } else if (g_strcmp0 (attr->key, "recvonly") == 0) {
365 if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
366 GST_ERROR ("Multiple direction attributes");
367 return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
369 new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY;
370 } else if (g_strcmp0 (attr->key, "inactive") == 0) {
371 if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
372 GST_ERROR ("Multiple direction attributes");
373 return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
375 new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE;
382 #define DIR(val) GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_ ## val
383 GstWebRTCRTPTransceiverDirection
384 _intersect_answer_directions (GstWebRTCRTPTransceiverDirection offer,
385 GstWebRTCRTPTransceiverDirection answer)
387 if (offer == DIR (INACTIVE) || answer == DIR (INACTIVE))
388 return DIR (INACTIVE);
389 if (offer == DIR (SENDONLY) && answer == DIR (SENDRECV))
390 return DIR (RECVONLY);
391 if (offer == DIR (SENDONLY) && answer == DIR (RECVONLY))
392 return DIR (RECVONLY);
393 if (offer == DIR (RECVONLY) && answer == DIR (SENDRECV))
394 return DIR (SENDONLY);
395 if (offer == DIR (RECVONLY) && answer == DIR (SENDONLY))
396 return DIR (SENDONLY);
397 if (offer == DIR (SENDRECV) && answer == DIR (SENDRECV))
398 return DIR (SENDRECV);
399 if (offer == DIR (SENDRECV) && answer == DIR (SENDONLY))
400 return DIR (SENDONLY);
401 if (offer == DIR (SENDRECV) && answer == DIR (RECVONLY))
402 return DIR (RECVONLY);
403 if (offer == DIR (RECVONLY) && answer == DIR (RECVONLY))
404 return DIR (INACTIVE);
405 if (offer == DIR (SENDONLY) && answer == DIR (SENDONLY))
406 return DIR (INACTIVE);
412 _media_replace_direction (GstSDPMedia * media,
413 GstWebRTCRTPTransceiverDirection direction)
415 const gchar *dir_str;
418 dir_str = gst_webrtc_rtp_transceiver_direction_to_string (direction);
420 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
421 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
423 if (g_strcmp0 (attr->key, "sendonly") == 0
424 || g_strcmp0 (attr->key, "sendrecv") == 0
425 || g_strcmp0 (attr->key, "recvonly") == 0
426 || g_strcmp0 (attr->key, "inactive") == 0) {
427 GstSDPAttribute new_attr = { 0, };
428 GST_TRACE ("replace %s with %s", attr->key, dir_str);
429 gst_sdp_attribute_set (&new_attr, dir_str, "");
430 gst_sdp_media_replace_attribute (media, i, &new_attr);
435 GST_TRACE ("add %s", dir_str);
436 gst_sdp_media_add_attribute (media, dir_str, "");
439 GstWebRTCRTPTransceiverDirection
440 _get_final_direction (GstWebRTCRTPTransceiverDirection local_dir,
441 GstWebRTCRTPTransceiverDirection remote_dir)
443 GstWebRTCRTPTransceiverDirection new_dir;
444 new_dir = DIR (NONE);
447 new_dir = DIR (INACTIVE);
450 if (remote_dir == DIR (SENDONLY)) {
451 GST_ERROR ("remote SDP has the same directionality. "
452 "This is not legal.");
454 } else if (remote_dir == DIR (INACTIVE)) {
455 new_dir = DIR (INACTIVE);
457 new_dir = DIR (SENDONLY);
461 if (remote_dir == DIR (RECVONLY)) {
462 GST_ERROR ("remote SDP has the same directionality. "
463 "This is not legal.");
465 } else if (remote_dir == DIR (INACTIVE)) {
466 new_dir = DIR (INACTIVE);
468 new_dir = DIR (RECVONLY);
472 if (remote_dir == DIR (INACTIVE)) {
473 new_dir = DIR (INACTIVE);
474 } else if (remote_dir == DIR (SENDONLY)) {
475 new_dir = DIR (RECVONLY);
476 } else if (remote_dir == DIR (RECVONLY)) {
477 new_dir = DIR (SENDONLY);
478 } else if (remote_dir == DIR (SENDRECV)) {
479 new_dir = DIR (SENDRECV);
483 g_assert_not_reached ();
487 if (new_dir == DIR (NONE)) {
488 GST_ERROR ("Abnormal situation!");
497 #define SETUP(val) GST_WEBRTC_DTLS_SETUP_ ## val
499 _get_dtls_setup_from_media (const GstSDPMedia * media)
503 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
504 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
506 if (g_strcmp0 (attr->key, "setup") == 0) {
507 if (g_strcmp0 (attr->value, "actpass") == 0) {
508 return SETUP (ACTPASS);
509 } else if (g_strcmp0 (attr->value, "active") == 0) {
510 return SETUP (ACTIVE);
511 } else if (g_strcmp0 (attr->value, "passive") == 0) {
512 return SETUP (PASSIVE);
514 GST_ERROR ("unknown setup value %s", attr->value);
520 GST_LOG ("no setup attribute in media");
525 _intersect_dtls_setup (GstWebRTCDTLSSetup offer)
528 case SETUP (NONE): /* default is active */
529 case SETUP (ACTPASS):
530 case SETUP (PASSIVE):
531 return SETUP (ACTIVE);
533 return SETUP (PASSIVE);
540 _media_replace_setup (GstSDPMedia * media, GstWebRTCDTLSSetup setup)
542 const gchar *setup_str;
545 setup_str = _enum_value_to_string (GST_TYPE_WEBRTC_DTLS_SETUP, setup);
547 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
548 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
550 if (g_strcmp0 (attr->key, "setup") == 0) {
551 GstSDPAttribute new_attr = { 0, };
552 GST_TRACE ("replace setup:%s with setup:%s", attr->value, setup_str);
553 gst_sdp_attribute_set (&new_attr, "setup", setup_str);
554 gst_sdp_media_replace_attribute (media, i, &new_attr);
559 GST_TRACE ("add setup:%s", setup_str);
560 gst_sdp_media_add_attribute (media, "setup", setup_str);
564 _get_final_setup (GstWebRTCDTLSSetup local_setup,
565 GstWebRTCDTLSSetup remote_setup)
567 GstWebRTCDTLSSetup new_setup;
569 new_setup = SETUP (NONE);
570 switch (local_setup) {
572 /* someone's done a bad job of mangling the SDP. or bugs */
573 g_critical ("Received a locally generated sdp without a parseable "
574 "\'a=setup\' line. This indicates a bug somewhere. Bailing");
577 if (remote_setup == SETUP (ACTIVE)) {
578 GST_ERROR ("remote SDP has the same "
579 "\'a=setup:active\' attribute. This is not legal");
582 new_setup = SETUP (ACTIVE);
584 case SETUP (PASSIVE):
585 if (remote_setup == SETUP (PASSIVE)) {
586 GST_ERROR ("remote SDP has the same "
587 "\'a=setup:passive\' attribute. This is not legal");
590 new_setup = SETUP (PASSIVE);
592 case SETUP (ACTPASS):
593 if (remote_setup == SETUP (ACTPASS)) {
594 GST_ERROR ("remote SDP has the same "
595 "\'a=setup:actpass\' attribute. This is not legal");
598 if (remote_setup == SETUP (ACTIVE))
599 new_setup = SETUP (PASSIVE);
600 else if (remote_setup == SETUP (PASSIVE))
601 new_setup = SETUP (ACTIVE);
602 else if (remote_setup == SETUP (NONE)) {
603 /* XXX: what to do here? */
604 GST_WARNING ("unspecified situation. local: "
605 "\'a=setup:actpass\' remote: none/unparseable");
606 new_setup = SETUP (ACTIVE);
610 g_assert_not_reached ();
613 if (new_setup == SETUP (NONE)) {
614 GST_ERROR ("Abnormal situation!");
624 _generate_fingerprint_from_certificate (gchar * certificate,
625 GChecksumType checksum_type)
627 gchar **lines, *line;
628 guchar *tmp, *decoded, *digest;
630 GString *fingerprint;
631 gsize decoded_length, digest_size;
636 g_return_val_if_fail (certificate != NULL, NULL);
638 /* 1. decode the certificate removing newlines and the certificate header
640 decoded = tmp = g_new0 (guchar, (strlen (certificate) / 4) * 3 + 3);
641 lines = g_strsplit (certificate, "\n", 0);
642 for (i = 0, line = lines[i]; line; line = lines[++i]) {
643 if (line[0] && !g_str_has_prefix (line, "-----"))
644 tmp += g_base64_decode_step (line, strlen (line), tmp, &state, &save);
647 decoded_length = tmp - decoded;
649 /* 2. compute a checksum of the decoded certificate */
650 checksum = g_checksum_new (checksum_type);
651 digest_size = g_checksum_type_get_length (checksum_type);
652 digest = g_new (guint8, digest_size);
653 g_checksum_update (checksum, decoded, decoded_length);
654 g_checksum_get_digest (checksum, digest, &digest_size);
657 /* 3. hex encode the checksum separated with ':'s */
658 fingerprint = g_string_new (NULL);
659 for (i = 0; i < digest_size; i++) {
661 g_string_append (fingerprint, ":");
662 g_string_append_printf (fingerprint, "%02X", digest[i]);
666 g_checksum_free (checksum);
668 return g_string_free (fingerprint, FALSE);
671 #define DEFAULT_ICE_UFRAG_LEN 32
672 #define DEFAULT_ICE_PASSWORD_LEN 32
673 static const gchar *ice_credential_chars =
674 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/";
677 _generate_ice_credentials (gchar ** ufrag, gchar ** password)
681 *ufrag = g_malloc0 (DEFAULT_ICE_UFRAG_LEN + 1);
682 for (i = 0; i < DEFAULT_ICE_UFRAG_LEN; i++)
684 ice_credential_chars[g_random_int_range (0,
685 strlen (ice_credential_chars))];
687 *password = g_malloc0 (DEFAULT_ICE_PASSWORD_LEN + 1);
688 for (i = 0; i < DEFAULT_ICE_PASSWORD_LEN; i++)
690 ice_credential_chars[g_random_int_range (0,
691 strlen (ice_credential_chars))];
695 _get_sctp_port_from_media (const GstSDPMedia * media)
701 if (gst_sdp_media_formats_len (media) != 1) {
702 /* only exactly one format is supported */
706 format = gst_sdp_media_get_format (media, 0);
708 if (g_strcmp0 (format, "webrtc-datachannel") == 0) {
709 /* draft-ietf-mmusic-sctp-sdp-21, e.g. Firefox 63 and later */
711 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
712 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
714 if (g_strcmp0 (attr->key, "sctp-port") == 0) {
715 gint64 port = g_ascii_strtoll (attr->value, &endptr, 10);
716 if (endptr == attr->value) {
717 /* conversion error */
724 /* draft-ietf-mmusic-sctp-sdp-05, e.g. Chrome as recent as 75 */
725 gint64 port = g_ascii_strtoll (format, &endptr, 10);
726 if (endptr == format) {
727 /* conversion error */
731 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
732 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
734 if (g_strcmp0 (attr->key, "sctpmap") == 0 && atoi (attr->value) == port) {
735 /* a=sctpmap:5000 webrtc-datachannel 256 */
736 gchar **parts = g_strsplit (attr->value, " ", 3);
737 if (!parts[1] || g_strcmp0 (parts[1], "webrtc-datachannel") != 0) {
750 _get_sctp_max_message_size_from_media (const GstSDPMedia * media)
754 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
755 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
757 if (g_strcmp0 (attr->key, "max-message-size") == 0)
758 return atoi (attr->value);
765 _message_media_is_datachannel (const GstSDPMessage * msg, guint media_id)
767 const GstSDPMedia *media;
772 if (gst_sdp_message_medias_len (msg) <= media_id)
775 media = gst_sdp_message_get_media (msg, media_id);
777 if (g_strcmp0 (gst_sdp_media_get_media (media), "application") != 0)
780 if (gst_sdp_media_formats_len (media) != 1)
783 if (g_strcmp0 (gst_sdp_media_get_format (media, 0),
784 "webrtc-datachannel") != 0)
791 _message_get_datachannel_index (const GstSDPMessage * msg)
795 for (i = 0; i < gst_sdp_message_medias_len (msg); i++) {
796 if (_message_media_is_datachannel (msg, i)) {
797 g_assert (i < G_MAXUINT);
806 _get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
807 gchar ** ufrag, gchar ** pwd)
815 /* search in the corresponding media section */
816 const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx);
817 const gchar *tmp_ufrag =
818 gst_sdp_media_get_attribute_val (media, "ice-ufrag");
819 const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
820 if (tmp_ufrag && tmp_pwd) {
821 *ufrag = g_strdup (tmp_ufrag);
822 *pwd = g_strdup (tmp_pwd);
827 /* then in the sdp message itself */
828 for (i = 0; i < gst_sdp_message_attributes_len (sdp); i++) {
829 const GstSDPAttribute *attr = gst_sdp_message_get_attribute (sdp, i);
831 if (g_strcmp0 (attr->key, "ice-ufrag") == 0) {
833 *ufrag = g_strdup (attr->value);
834 } else if (g_strcmp0 (attr->key, "ice-pwd") == 0) {
836 *pwd = g_strdup (attr->value);
839 if (!*ufrag && !*pwd) {
840 /* Check in the medias themselves. According to JSEP, they should be
841 * identical FIXME: only for bundle-d streams */
842 for (i = 0; i < gst_sdp_message_medias_len (sdp); i++) {
843 const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
844 const gchar *tmp_ufrag =
845 gst_sdp_media_get_attribute_val (media, "ice-ufrag");
846 const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
847 if (tmp_ufrag && tmp_pwd) {
848 *ufrag = g_strdup (tmp_ufrag);
849 *pwd = g_strdup (tmp_pwd);
857 _parse_bundle (GstSDPMessage * sdp, GStrv * bundled, GError ** error)
860 gboolean ret = FALSE;
862 group = gst_sdp_message_get_attribute_val (sdp, "group");
864 if (group && g_str_has_prefix (group, "BUNDLE ")) {
865 *bundled = g_strsplit (group + strlen ("BUNDLE "), " ", 0);
867 if (!(*bundled)[0]) {
868 g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
869 "Invalid format for BUNDLE group, expected at least one mid (%s)",
871 g_strfreev (*bundled);
887 _get_bundle_index (GstSDPMessage * sdp, GStrv bundled, guint * idx)
889 gboolean ret = FALSE;
892 for (i = 0; i < gst_sdp_message_medias_len (sdp); i++) {
893 const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
894 const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
896 if (!g_strcmp0 (mid, bundled[0])) {
907 _media_is_bundle_only (const GstSDPMedia * media)
911 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
912 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
914 if (g_strcmp0 (attr->key, "bundle-only") == 0) {