webrtcbin: an element that handles the transport aspects of webrtc connections
[platform/upstream/gst-plugins-bad.git] / ext / webrtc / webrtcsdp.c
1 /* GStreamer
2  * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
3  *
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.
8  *
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.
13  *
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.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #include "webrtcsdp.h"
25
26 #include "utils.h"
27 #include "gstwebrtcbin.h"
28
29 #include <string.h>
30
31 #define IS_EMPTY_SDP_ATTRIBUTE(val) (val == NULL || g_strcmp0(val, "") == 0)
32
33 const gchar *
34 _sdp_source_to_string (SDPSource source)
35 {
36   switch (source) {
37     case SDP_LOCAL:
38       return "local";
39     case SDP_REMOTE:
40       return "remote";
41     default:
42       return "none";
43   }
44 }
45
46 static gboolean
47 _check_valid_state_for_sdp_change (GstWebRTCBin * webrtc, SDPSource source,
48     GstWebRTCSDPType type, GError ** error)
49 {
50   GstWebRTCSignalingState state = webrtc->signaling_state;
51 #define STATE(val) GST_WEBRTC_SIGNALING_STATE_ ## val
52 #define TYPE(val) GST_WEBRTC_SDP_TYPE_ ## val
53
54   if (source == SDP_LOCAL && type == TYPE (OFFER) && state == STATE (STABLE))
55     return TRUE;
56   if (source == SDP_LOCAL && type == TYPE (OFFER)
57       && state == STATE (HAVE_LOCAL_OFFER))
58     return TRUE;
59   if (source == SDP_LOCAL && type == TYPE (ANSWER)
60       && state == STATE (HAVE_REMOTE_OFFER))
61     return TRUE;
62   if (source == SDP_LOCAL && type == TYPE (PRANSWER)
63       && state == STATE (HAVE_REMOTE_OFFER))
64     return TRUE;
65   if (source == SDP_LOCAL && type == TYPE (PRANSWER)
66       && state == STATE (HAVE_LOCAL_PRANSWER))
67     return TRUE;
68
69   if (source == SDP_REMOTE && type == TYPE (OFFER) && state == STATE (STABLE))
70     return TRUE;
71   if (source == SDP_REMOTE && type == TYPE (OFFER)
72       && state == STATE (HAVE_REMOTE_OFFER))
73     return TRUE;
74   if (source == SDP_REMOTE && type == TYPE (ANSWER)
75       && state == STATE (HAVE_LOCAL_OFFER))
76     return TRUE;
77   if (source == SDP_REMOTE && type == TYPE (PRANSWER)
78       && state == STATE (HAVE_LOCAL_OFFER))
79     return TRUE;
80   if (source == SDP_REMOTE && type == TYPE (PRANSWER)
81       && state == STATE (HAVE_REMOTE_PRANSWER))
82     return TRUE;
83
84   {
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);
92     g_free (state);
93     g_free (type_str);
94   }
95
96   return FALSE;
97
98 #undef STATE
99 #undef TYPE
100 }
101
102 static gboolean
103 _check_sdp_crypto (GstWebRTCBin * webrtc, SDPSource source,
104     GstWebRTCSessionDescription * sdp, GError ** error)
105 {
106   const gchar *message_fingerprint, *fingerprint;
107   const GstSDPKey *key;
108   int i;
109
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");
114     return FALSE;
115   }
116
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");
123
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);
129       return FALSE;
130     }
131     if (IS_EMPTY_SDP_ATTRIBUTE (fingerprint)) {
132       fingerprint = media_fingerprint;
133     }
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);
141       return FALSE;
142     }
143   }
144
145   return TRUE;
146 }
147
148 #if 0
149 static gboolean
150 _session_has_attribute_key (const GstSDPMessage * msg, const gchar * key)
151 {
152   int i;
153   for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
154     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
155
156     if (g_strcmp0 (attr->key, key) == 0)
157       return TRUE;
158   }
159
160   return FALSE;
161 }
162
163 static gboolean
164 _session_has_attribute_key_value (const GstSDPMessage * msg, const gchar * key,
165     const gchar * value)
166 {
167   int i;
168   for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
169     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
170
171     if (g_strcmp0 (attr->key, key) == 0 && g_strcmp0 (attr->value, value) == 0)
172       return TRUE;
173   }
174
175   return FALSE;
176 }
177
178 static gboolean
179 _check_trickle_ice (GstSDPMessage * msg, GError ** error)
180 {
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");
185   }
186   return TRUE;
187 }
188 #endif
189 gboolean
190 _media_has_attribute_key (const GstSDPMedia * media, const gchar * key)
191 {
192   int i;
193   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
194     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
195
196     if (g_strcmp0 (attr->key, key) == 0)
197       return TRUE;
198   }
199
200   return FALSE;
201 }
202
203 static gboolean
204 _media_has_mid (const GstSDPMedia * media, guint media_idx, GError ** error)
205 {
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",
210         media_idx);
211     return FALSE;
212   }
213   return TRUE;
214 }
215
216 static const gchar *
217 _media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
218 {
219   const gchar *ice_ufrag;
220
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))
226       return NULL;
227   }
228   return ice_ufrag;
229 }
230
231 static const gchar *
232 _media_get_ice_pwd (const GstSDPMessage * msg, guint media_idx)
233 {
234   const gchar *ice_pwd;
235
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))
241       return NULL;
242   }
243   return ice_pwd;
244 }
245
246 static gboolean
247 _media_has_setup (const GstSDPMedia * media, guint media_idx, GError ** error)
248 {
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",
254         media_idx);
255     return FALSE;
256   }
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,
260         setup);
261     return FALSE;
262   }
263   return TRUE;
264 }
265
266 #if 0
267 static gboolean
268 _media_has_dtls_id (const GstSDPMedia * media, guint media_idx, GError ** error)
269 {
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",
274         media_idx);
275     return FALSE;
276   }
277   return TRUE;
278 }
279 #endif
280 gboolean
281 validate_sdp (GstWebRTCBin * webrtc, SDPSource source,
282     GstWebRTCSessionDescription * sdp, GError ** error)
283 {
284 #if 0
285   const gchar *group, *bundle_ice_ufrag = NULL, *bundle_ice_pwd = NULL;
286   gchar **group_members = NULL;
287   gboolean is_bundle = FALSE;
288 #endif
289   int i;
290
291   if (!_check_valid_state_for_sdp_change (webrtc, source, sdp->type, error))
292     return FALSE;
293   if (!_check_sdp_crypto (webrtc, source, sdp, error))
294     return FALSE;
295 /* not explicitly required
296   if (ICE && !_check_trickle_ice (sdp->sdp))
297     return FALSE;
298   group = gst_sdp_message_get_attribute_val (sdp->sdp, "group");
299   is_bundle = g_str_has_prefix (group, "BUNDLE");
300   if (is_bundle)
301     group_members = g_strsplit (&group[6], " ", -1);*/
302
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);
305 #if 0
306     const gchar *mid;
307     gboolean media_in_bundle = FALSE, first_media_in_bundle = FALSE;
308     gboolean bundle_only = FALSE;
309 #endif
310     if (!_media_has_mid (media, i, error))
311       goto fail;
312 #if 0
313     mid = gst_sdp_media_get_attribute_val (media, "mid");
314     media_in_bundle = is_bundle && g_strv_contains (group_members, mid);
315     if (media_in_bundle)
316       bundle_only =
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;
320 #endif
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",
324           i);
325       goto fail;
326     }
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);
330       goto fail;
331     }
332     if (!_media_has_setup (media, i, error))
333       goto fail;
334 #if 0
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);
346         goto fail;
347       }
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);
354         goto fail;
355       }
356     }
357 #endif
358   }
359
360 //  g_strv_free (group_members);
361
362   return TRUE;
363
364 fail:
365 //  g_strv_free (group_members);
366   return FALSE;
367 }
368
369 GstWebRTCRTPTransceiverDirection
370 _get_direction_from_media (const GstSDPMedia * media)
371 {
372   GstWebRTCRTPTransceiverDirection new_dir =
373       GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
374   int i;
375
376   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
377     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
378
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;
383       }
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;
389       }
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;
395       }
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;
401       }
402       new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE;
403     }
404   }
405
406   return new_dir;
407 }
408
409 #define DIR(val) GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_ ## val
410 GstWebRTCRTPTransceiverDirection
411 _intersect_answer_directions (GstWebRTCRTPTransceiverDirection offer,
412     GstWebRTCRTPTransceiverDirection answer)
413 {
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);
428
429   return DIR (NONE);
430 }
431
432 void
433 _media_replace_direction (GstSDPMedia * media,
434     GstWebRTCRTPTransceiverDirection direction)
435 {
436   gchar *dir_str;
437   int i;
438
439   dir_str =
440       _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
441       direction);
442
443   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
444     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
445
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);
453       return;
454     }
455   }
456
457   GST_TRACE ("add %s", dir_str);
458   gst_sdp_media_add_attribute (media, dir_str, "");
459   g_free (dir_str);
460 }
461
462 GstWebRTCRTPTransceiverDirection
463 _get_final_direction (GstWebRTCRTPTransceiverDirection local_dir,
464     GstWebRTCRTPTransceiverDirection remote_dir)
465 {
466   GstWebRTCRTPTransceiverDirection new_dir;
467   new_dir = DIR (NONE);
468   switch (local_dir) {
469     case DIR (INACTIVE):
470       new_dir = DIR (INACTIVE);
471       break;
472     case DIR (SENDONLY):
473       if (remote_dir == DIR (SENDONLY)) {
474         GST_ERROR ("remote SDP has the same directionality. "
475             "This is not legal.");
476         return DIR (NONE);
477       } else if (remote_dir == DIR (INACTIVE)) {
478         new_dir = DIR (INACTIVE);
479       } else {
480         new_dir = DIR (SENDONLY);
481       }
482       break;
483     case DIR (RECVONLY):
484       if (remote_dir == DIR (RECVONLY)) {
485         GST_ERROR ("remote SDP has the same directionality. "
486             "This is not legal.");
487         return DIR (NONE);
488       } else if (remote_dir == DIR (INACTIVE)) {
489         new_dir = DIR (INACTIVE);
490       } else {
491         new_dir = DIR (RECVONLY);
492       }
493       break;
494     case DIR (SENDRECV):
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);
503       }
504       break;
505     default:
506       g_assert_not_reached ();
507       break;
508   }
509
510   if (new_dir == DIR (NONE)) {
511     GST_ERROR ("Abnormal situation!");
512     return DIR (NONE);
513   }
514
515   return new_dir;
516 }
517
518 #undef DIR
519
520 #define SETUP(val) GST_WEBRTC_DTLS_SETUP_ ## val
521 GstWebRTCDTLSSetup
522 _get_dtls_setup_from_media (const GstSDPMedia * media)
523 {
524   int i;
525
526   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
527     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
528
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);
536       } else {
537         GST_ERROR ("unknown setup value %s", attr->value);
538         return SETUP (NONE);
539       }
540     }
541   }
542
543   GST_LOG ("no setup attribute in media");
544   return SETUP (NONE);
545 }
546
547 GstWebRTCDTLSSetup
548 _intersect_dtls_setup (GstWebRTCDTLSSetup offer)
549 {
550   switch (offer) {
551     case SETUP (NONE):         /* default is active */
552     case SETUP (ACTPASS):
553     case SETUP (PASSIVE):
554       return SETUP (ACTIVE);
555     case SETUP (ACTIVE):
556       return SETUP (PASSIVE);
557     default:
558       return SETUP (NONE);
559   }
560 }
561
562 void
563 _media_replace_setup (GstSDPMedia * media, GstWebRTCDTLSSetup setup)
564 {
565   gchar *setup_str;
566   int i;
567
568   setup_str = _enum_value_to_string (GST_TYPE_WEBRTC_DTLS_SETUP, setup);
569
570   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
571     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
572
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);
578       return;
579     }
580   }
581
582   GST_TRACE ("add setup:%s", setup_str);
583   gst_sdp_media_add_attribute (media, "setup", setup_str);
584   g_free (setup_str);
585 }
586
587 GstWebRTCDTLSSetup
588 _get_final_setup (GstWebRTCDTLSSetup local_setup,
589     GstWebRTCDTLSSetup remote_setup)
590 {
591   GstWebRTCDTLSSetup new_setup;
592
593   new_setup = SETUP (NONE);
594   switch (local_setup) {
595     case SETUP (NONE):
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");
599       return SETUP (NONE);
600     case SETUP (ACTIVE):
601       if (remote_setup == SETUP (ACTIVE)) {
602         GST_ERROR ("remote SDP has the same "
603             "\'a=setup:active\' attribute. This is not legal");
604         return SETUP (NONE);
605       }
606       new_setup = SETUP (ACTIVE);
607       break;
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");
612         return SETUP (NONE);
613       }
614       new_setup = SETUP (PASSIVE);
615       break;
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");
620         return SETUP (NONE);
621       }
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);
631       }
632       break;
633     default:
634       g_assert_not_reached ();
635       return SETUP (NONE);
636   }
637   if (new_setup == SETUP (NONE)) {
638     GST_ERROR ("Abnormal situation!");
639     return SETUP (NONE);
640   }
641
642   return new_setup;
643 }
644
645 #undef SETUP
646
647 gchar *
648 _generate_fingerprint_from_certificate (gchar * certificate,
649     GChecksumType checksum_type)
650 {
651   gchar **lines, *line;
652   guchar *tmp, *decoded, *digest;
653   GChecksum *checksum;
654   GString *fingerprint;
655   gsize decoded_length, digest_size;
656   gint state = 0;
657   guint save = 0;
658   int i;
659
660   g_return_val_if_fail (certificate != NULL, NULL);
661
662   /* 1. decode the certificate removing newlines and the certificate header
663    * and footer */
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);
669   }
670   g_strfreev (lines);
671   decoded_length = tmp - decoded;
672
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);
679   g_free (decoded);
680
681   /* 3. hex encode the checksum separated with ':'s */
682   fingerprint = g_string_new (NULL);
683   for (i = 0; i < digest_size; i++) {
684     if (i)
685       g_string_append (fingerprint, ":");
686     g_string_append_printf (fingerprint, "%02X", digest[i]);
687   }
688
689   g_free (digest);
690   g_checksum_free (checksum);
691
692   return g_string_free (fingerprint, FALSE);
693 }
694
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" "+/";
699
700 void
701 _generate_ice_credentials (gchar ** ufrag, gchar ** password)
702 {
703   int i;
704
705   *ufrag = g_malloc0 (DEFAULT_ICE_UFRAG_LEN + 1);
706   for (i = 0; i < DEFAULT_ICE_UFRAG_LEN; i++)
707     (*ufrag)[i] =
708         ice_credential_chars[g_random_int_range (0,
709             strlen (ice_credential_chars))];
710
711   *password = g_malloc0 (DEFAULT_ICE_PASSWORD_LEN + 1);
712   for (i = 0; i < DEFAULT_ICE_PASSWORD_LEN; i++)
713     (*password)[i] =
714         ice_credential_chars[g_random_int_range (0,
715             strlen (ice_credential_chars))];
716 }