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"
27 #include "gstwebrtcbin.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 (GstWebRTCBin * webrtc, SDPSource source,
48 GstWebRTCSDPType type, GError ** error)
50 GstWebRTCSignalingState state = webrtc->signaling_state;
51 #define STATE(val) GST_WEBRTC_SIGNALING_STATE_ ## val
52 #define TYPE(val) GST_WEBRTC_SDP_TYPE_ ## val
54 if (source == SDP_LOCAL && type == TYPE (OFFER) && state == STATE (STABLE))
56 if (source == SDP_LOCAL && type == TYPE (OFFER)
57 && state == STATE (HAVE_LOCAL_OFFER))
59 if (source == SDP_LOCAL && type == TYPE (ANSWER)
60 && state == STATE (HAVE_REMOTE_OFFER))
62 if (source == SDP_LOCAL && type == TYPE (PRANSWER)
63 && state == STATE (HAVE_REMOTE_OFFER))
65 if (source == SDP_LOCAL && type == TYPE (PRANSWER)
66 && state == STATE (HAVE_LOCAL_PRANSWER))
69 if (source == SDP_REMOTE && type == TYPE (OFFER) && state == STATE (STABLE))
71 if (source == SDP_REMOTE && type == TYPE (OFFER)
72 && state == STATE (HAVE_REMOTE_OFFER))
74 if (source == SDP_REMOTE && type == TYPE (ANSWER)
75 && state == STATE (HAVE_LOCAL_OFFER))
77 if (source == SDP_REMOTE && type == TYPE (PRANSWER)
78 && state == STATE (HAVE_LOCAL_OFFER))
80 if (source == SDP_REMOTE && type == TYPE (PRANSWER)
81 && state == STATE (HAVE_REMOTE_PRANSWER))
85 gchar *state = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
86 webrtc->signaling_state);
87 gchar *type_str = _enum_value_to_string (GST_TYPE_WEBRTC_SDP_TYPE, type);
88 g_set_error (error, GST_WEBRTC_BIN_ERROR,
89 GST_WEBRTC_BIN_ERROR_INVALID_STATE,
90 "Not in the correct state (%s) for setting %s %s description", state,
91 _sdp_source_to_string (source), type_str);
103 _check_sdp_crypto (GstWebRTCBin * webrtc, SDPSource source,
104 GstWebRTCSessionDescription * sdp, GError ** error)
106 const gchar *message_fingerprint, *fingerprint;
107 const GstSDPKey *key;
110 key = gst_sdp_message_get_key (sdp->sdp);
111 if (!IS_EMPTY_SDP_ATTRIBUTE (key->data)) {
112 g_set_error_literal (error, GST_WEBRTC_BIN_ERROR,
113 GST_WEBRTC_BIN_ERROR_BAD_SDP, "sdp contains a k line");
117 message_fingerprint = fingerprint =
118 gst_sdp_message_get_attribute_val (sdp->sdp, "fingerprint");
119 for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
120 const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i);
121 const gchar *media_fingerprint =
122 gst_sdp_media_get_attribute_val (media, "fingerprint");
124 if (!IS_EMPTY_SDP_ATTRIBUTE (message_fingerprint)
125 && !IS_EMPTY_SDP_ATTRIBUTE (media_fingerprint)) {
126 g_set_error (error, GST_WEBRTC_BIN_ERROR,
127 GST_WEBRTC_BIN_ERROR_FINGERPRINT,
128 "No fingerprint lines in sdp for media %u", i);
131 if (IS_EMPTY_SDP_ATTRIBUTE (fingerprint)) {
132 fingerprint = media_fingerprint;
134 if (!IS_EMPTY_SDP_ATTRIBUTE (media_fingerprint)
135 && g_strcmp0 (fingerprint, media_fingerprint) != 0) {
136 g_set_error (error, GST_WEBRTC_BIN_ERROR,
137 GST_WEBRTC_BIN_ERROR_FINGERPRINT,
138 "Fingerprint in media %u differs from %s fingerprint. "
139 "\'%s\' != \'%s\'", i, message_fingerprint ? "global" : "previous",
140 fingerprint, media_fingerprint);
150 _session_has_attribute_key (const GstSDPMessage * msg, const gchar * key)
153 for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
154 const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
156 if (g_strcmp0 (attr->key, key) == 0)
164 _session_has_attribute_key_value (const GstSDPMessage * msg, const gchar * key,
168 for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
169 const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
171 if (g_strcmp0 (attr->key, key) == 0 && g_strcmp0 (attr->value, value) == 0)
179 _check_trickle_ice (GstSDPMessage * msg, GError ** error)
181 if (!_session_has_attribute_key_value (msg, "ice-options", "trickle")) {
182 g_set_error_literal (error, GST_WEBRTC_BIN_ERROR,
183 GST_WEBRTC_BIN_ERROR_BAD_SDP,
184 "No required \'a=ice-options:trickle\' line in sdp");
190 _media_has_attribute_key (const GstSDPMedia * media, const gchar * key)
193 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
194 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
196 if (g_strcmp0 (attr->key, key) == 0)
204 _media_has_mid (const GstSDPMedia * media, guint media_idx, GError ** error)
206 const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
207 if (IS_EMPTY_SDP_ATTRIBUTE (mid)) {
208 g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
209 "media %u is missing or contains an empty \'mid\' attribute",
217 _media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
219 const gchar *ice_ufrag;
221 ice_ufrag = gst_sdp_message_get_attribute_val (msg, "ice-ufrag");
222 if (IS_EMPTY_SDP_ATTRIBUTE (ice_ufrag)) {
223 const GstSDPMedia *media = gst_sdp_message_get_media (msg, media_idx);
224 ice_ufrag = gst_sdp_media_get_attribute_val (media, "ice-ufrag");
225 if (IS_EMPTY_SDP_ATTRIBUTE (ice_ufrag))
232 _media_get_ice_pwd (const GstSDPMessage * msg, guint media_idx)
234 const gchar *ice_pwd;
236 ice_pwd = gst_sdp_message_get_attribute_val (msg, "ice-pwd");
237 if (IS_EMPTY_SDP_ATTRIBUTE (ice_pwd)) {
238 const GstSDPMedia *media = gst_sdp_message_get_media (msg, media_idx);
239 ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
240 if (IS_EMPTY_SDP_ATTRIBUTE (ice_pwd))
247 _media_has_setup (const GstSDPMedia * media, guint media_idx, GError ** error)
249 static const gchar *valid_setups[] = { "actpass", "active", "passive", NULL };
250 const gchar *setup = gst_sdp_media_get_attribute_val (media, "setup");
251 if (IS_EMPTY_SDP_ATTRIBUTE (setup)) {
252 g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
253 "media %u is missing or contains an empty \'setup\' attribute",
257 if (!g_strv_contains (valid_setups, setup)) {
258 g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
259 "media %u contains unknown \'setup\' attribute, \'%s\'", media_idx,
268 _media_has_dtls_id (const GstSDPMedia * media, guint media_idx, GError ** error)
270 const gchar *dtls_id = gst_sdp_media_get_attribute_val (media, "ice-pwd");
271 if (IS_EMPTY_SDP_ATTRIBUTE (dtls_id)) {
272 g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
273 "media %u is missing or contains an empty \'dtls-id\' attribute",
281 validate_sdp (GstWebRTCBin * webrtc, SDPSource source,
282 GstWebRTCSessionDescription * sdp, GError ** error)
285 const gchar *group, *bundle_ice_ufrag = NULL, *bundle_ice_pwd = NULL;
286 gchar **group_members = NULL;
287 gboolean is_bundle = FALSE;
291 if (!_check_valid_state_for_sdp_change (webrtc, source, sdp->type, error))
293 if (!_check_sdp_crypto (webrtc, source, sdp, error))
295 /* not explicitly required
296 if (ICE && !_check_trickle_ice (sdp->sdp))
298 group = gst_sdp_message_get_attribute_val (sdp->sdp, "group");
299 is_bundle = g_str_has_prefix (group, "BUNDLE");
301 group_members = g_strsplit (&group[6], " ", -1);*/
303 for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
304 const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i);
307 gboolean media_in_bundle = FALSE, first_media_in_bundle = FALSE;
308 gboolean bundle_only = FALSE;
310 if (!_media_has_mid (media, i, error))
313 mid = gst_sdp_media_get_attribute_val (media, "mid");
314 media_in_bundle = is_bundle && g_strv_contains (group_members, mid);
317 gst_sdp_media_get_attribute_val (media, "bundle-only") != NULL;
318 first_media_in_bundle = media_in_bundle
319 && g_strcmp0 (mid, group_members[0]) == 0;
321 if (!_media_get_ice_ufrag (sdp->sdp, i)) {
322 g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
323 "media %u is missing or contains an empty \'ice-ufrag\' attribute",
327 if (!_media_get_ice_pwd (sdp->sdp, i)) {
328 g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
329 "media %u is missing or contains an empty \'ice-pwd\' attribute", i);
332 if (!_media_has_setup (media, i, error))
335 /* check paramaters in bundle are the same */
336 if (media_in_bundle) {
337 const gchar *ice_ufrag =
338 gst_sdp_media_get_attribute_val (media, "ice-ufrag");
339 const gchar *ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
340 if (!bundle_ice_ufrag)
341 bundle_ice_ufrag = ice_ufrag;
342 else if (!g_strcmp0 (bundle_ice_ufrag, ice_ufrag) != 0) {
343 g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
344 "media %u has different ice-ufrag values in bundle. "
345 "%s != %s", i, bundle_ice_ufrag, ice_ufrag);
348 if (!bundle_ice_pwd) {
349 bundle_ice_pwd = ice_pwd;
350 } else if (g_strcmp0 (bundle_ice_pwd, ice_pwd) == 0) {
351 g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
352 "media %u has different ice-ufrag values in bundle. "
353 "%s != %s", i, bundle_ice_ufrag, ice_ufrag);
360 // g_strv_free (group_members);
365 // g_strv_free (group_members);
369 GstWebRTCRTPTransceiverDirection
370 _get_direction_from_media (const GstSDPMedia * media)
372 GstWebRTCRTPTransceiverDirection new_dir =
373 GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
376 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
377 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
379 if (g_strcmp0 (attr->key, "sendonly") == 0) {
380 if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
381 GST_ERROR ("Multiple direction attributes");
382 return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
384 new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
385 } else if (g_strcmp0 (attr->key, "sendrecv") == 0) {
386 if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
387 GST_ERROR ("Multiple direction attributes");
388 return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
390 new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV;
391 } else if (g_strcmp0 (attr->key, "recvonly") == 0) {
392 if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
393 GST_ERROR ("Multiple direction attributes");
394 return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
396 new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY;
397 } else if (g_strcmp0 (attr->key, "inactive") == 0) {
398 if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
399 GST_ERROR ("Multiple direction attributes");
400 return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
402 new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE;
409 #define DIR(val) GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_ ## val
410 GstWebRTCRTPTransceiverDirection
411 _intersect_answer_directions (GstWebRTCRTPTransceiverDirection offer,
412 GstWebRTCRTPTransceiverDirection answer)
414 if (offer == DIR (SENDONLY) && answer == DIR (SENDRECV))
415 return DIR (RECVONLY);
416 if (offer == DIR (SENDONLY) && answer == DIR (RECVONLY))
417 return DIR (RECVONLY);
418 if (offer == DIR (RECVONLY) && answer == DIR (SENDRECV))
419 return DIR (SENDONLY);
420 if (offer == DIR (RECVONLY) && answer == DIR (SENDONLY))
421 return DIR (SENDONLY);
422 if (offer == DIR (SENDRECV) && answer == DIR (SENDRECV))
423 return DIR (SENDRECV);
424 if (offer == DIR (SENDRECV) && answer == DIR (SENDONLY))
425 return DIR (SENDONLY);
426 if (offer == DIR (SENDRECV) && answer == DIR (RECVONLY))
427 return DIR (RECVONLY);
433 _media_replace_direction (GstSDPMedia * media,
434 GstWebRTCRTPTransceiverDirection direction)
440 _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
443 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
444 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
446 if (g_strcmp0 (attr->key, "sendonly") == 0
447 || g_strcmp0 (attr->key, "sendrecv") == 0
448 || g_strcmp0 (attr->key, "recvonly") == 0) {
449 GstSDPAttribute new_attr = { 0, };
450 GST_TRACE ("replace %s with %s", attr->key, dir_str);
451 gst_sdp_attribute_set (&new_attr, dir_str, "");
452 gst_sdp_media_replace_attribute (media, i, &new_attr);
457 GST_TRACE ("add %s", dir_str);
458 gst_sdp_media_add_attribute (media, dir_str, "");
462 GstWebRTCRTPTransceiverDirection
463 _get_final_direction (GstWebRTCRTPTransceiverDirection local_dir,
464 GstWebRTCRTPTransceiverDirection remote_dir)
466 GstWebRTCRTPTransceiverDirection new_dir;
467 new_dir = DIR (NONE);
470 new_dir = DIR (INACTIVE);
473 if (remote_dir == DIR (SENDONLY)) {
474 GST_ERROR ("remote SDP has the same directionality. "
475 "This is not legal.");
477 } else if (remote_dir == DIR (INACTIVE)) {
478 new_dir = DIR (INACTIVE);
480 new_dir = DIR (SENDONLY);
484 if (remote_dir == DIR (RECVONLY)) {
485 GST_ERROR ("remote SDP has the same directionality. "
486 "This is not legal.");
488 } else if (remote_dir == DIR (INACTIVE)) {
489 new_dir = DIR (INACTIVE);
491 new_dir = DIR (RECVONLY);
495 if (remote_dir == DIR (INACTIVE)) {
496 new_dir = DIR (INACTIVE);
497 } else if (remote_dir == DIR (SENDONLY)) {
498 new_dir = DIR (RECVONLY);
499 } else if (remote_dir == DIR (RECVONLY)) {
500 new_dir = DIR (SENDONLY);
501 } else if (remote_dir == DIR (SENDRECV)) {
502 new_dir = DIR (SENDRECV);
506 g_assert_not_reached ();
510 if (new_dir == DIR (NONE)) {
511 GST_ERROR ("Abnormal situation!");
520 #define SETUP(val) GST_WEBRTC_DTLS_SETUP_ ## val
522 _get_dtls_setup_from_media (const GstSDPMedia * media)
526 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
527 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
529 if (g_strcmp0 (attr->key, "setup") == 0) {
530 if (g_strcmp0 (attr->value, "actpass") == 0) {
531 return SETUP (ACTPASS);
532 } else if (g_strcmp0 (attr->value, "active") == 0) {
533 return SETUP (ACTIVE);
534 } else if (g_strcmp0 (attr->value, "passive") == 0) {
535 return SETUP (PASSIVE);
537 GST_ERROR ("unknown setup value %s", attr->value);
543 GST_LOG ("no setup attribute in media");
548 _intersect_dtls_setup (GstWebRTCDTLSSetup offer)
551 case SETUP (NONE): /* default is active */
552 case SETUP (ACTPASS):
553 case SETUP (PASSIVE):
554 return SETUP (ACTIVE);
556 return SETUP (PASSIVE);
563 _media_replace_setup (GstSDPMedia * media, GstWebRTCDTLSSetup setup)
568 setup_str = _enum_value_to_string (GST_TYPE_WEBRTC_DTLS_SETUP, setup);
570 for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
571 const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
573 if (g_strcmp0 (attr->key, "setup") == 0) {
574 GstSDPAttribute new_attr = { 0, };
575 GST_TRACE ("replace setup:%s with setup:%s", attr->value, setup_str);
576 gst_sdp_attribute_set (&new_attr, "setup", setup_str);
577 gst_sdp_media_replace_attribute (media, i, &new_attr);
582 GST_TRACE ("add setup:%s", setup_str);
583 gst_sdp_media_add_attribute (media, "setup", setup_str);
588 _get_final_setup (GstWebRTCDTLSSetup local_setup,
589 GstWebRTCDTLSSetup remote_setup)
591 GstWebRTCDTLSSetup new_setup;
593 new_setup = SETUP (NONE);
594 switch (local_setup) {
596 /* someone's done a bad job of mangling the SDP. or bugs */
597 g_critical ("Received a locally generated sdp without a parseable "
598 "\'a=setup\' line. This indicates a bug somewhere. Bailing");
601 if (remote_setup == SETUP (ACTIVE)) {
602 GST_ERROR ("remote SDP has the same "
603 "\'a=setup:active\' attribute. This is not legal");
606 new_setup = SETUP (ACTIVE);
608 case SETUP (PASSIVE):
609 if (remote_setup == SETUP (PASSIVE)) {
610 GST_ERROR ("remote SDP has the same "
611 "\'a=setup:passive\' attribute. This is not legal");
614 new_setup = SETUP (PASSIVE);
616 case SETUP (ACTPASS):
617 if (remote_setup == SETUP (ACTPASS)) {
618 GST_ERROR ("remote SDP has the same "
619 "\'a=setup:actpass\' attribute. This is not legal");
622 if (remote_setup == SETUP (ACTIVE))
623 new_setup = SETUP (PASSIVE);
624 else if (remote_setup == SETUP (PASSIVE))
625 new_setup = SETUP (ACTIVE);
626 else if (remote_setup == SETUP (NONE)) {
627 /* XXX: what to do here? */
628 GST_WARNING ("unspecified situation. local: "
629 "\'a=setup:actpass\' remote: none/unparseable");
630 new_setup = SETUP (ACTIVE);
634 g_assert_not_reached ();
637 if (new_setup == SETUP (NONE)) {
638 GST_ERROR ("Abnormal situation!");
648 _generate_fingerprint_from_certificate (gchar * certificate,
649 GChecksumType checksum_type)
651 gchar **lines, *line;
652 guchar *tmp, *decoded, *digest;
654 GString *fingerprint;
655 gsize decoded_length, digest_size;
660 g_return_val_if_fail (certificate != NULL, NULL);
662 /* 1. decode the certificate removing newlines and the certificate header
664 decoded = tmp = g_new0 (guchar, (strlen (certificate) / 4) * 3 + 3);
665 lines = g_strsplit (certificate, "\n", 0);
666 for (i = 0, line = lines[i]; line; line = lines[++i]) {
667 if (line[0] && !g_str_has_prefix (line, "-----"))
668 tmp += g_base64_decode_step (line, strlen (line), tmp, &state, &save);
671 decoded_length = tmp - decoded;
673 /* 2. compute a checksum of the decoded certificate */
674 checksum = g_checksum_new (checksum_type);
675 digest_size = g_checksum_type_get_length (checksum_type);
676 digest = g_new (guint8, digest_size);
677 g_checksum_update (checksum, decoded, decoded_length);
678 g_checksum_get_digest (checksum, digest, &digest_size);
681 /* 3. hex encode the checksum separated with ':'s */
682 fingerprint = g_string_new (NULL);
683 for (i = 0; i < digest_size; i++) {
685 g_string_append (fingerprint, ":");
686 g_string_append_printf (fingerprint, "%02X", digest[i]);
690 g_checksum_free (checksum);
692 return g_string_free (fingerprint, FALSE);
695 #define DEFAULT_ICE_UFRAG_LEN 32
696 #define DEFAULT_ICE_PASSWORD_LEN 32
697 static const gchar *ice_credential_chars =
698 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/";
701 _generate_ice_credentials (gchar ** ufrag, gchar ** password)
705 *ufrag = g_malloc0 (DEFAULT_ICE_UFRAG_LEN + 1);
706 for (i = 0; i < DEFAULT_ICE_UFRAG_LEN; i++)
708 ice_credential_chars[g_random_int_range (0,
709 strlen (ice_credential_chars))];
711 *password = g_malloc0 (DEFAULT_ICE_PASSWORD_LEN + 1);
712 for (i = 0; i < DEFAULT_ICE_PASSWORD_LEN; i++)
714 ice_credential_chars[g_random_int_range (0,
715 strlen (ice_credential_chars))];