documentation: fixed a heap o' typos
[platform/upstream/gstreamer.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
28 #include <string.h>
29 #include <stdlib.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 (GstWebRTCSignalingState state,
48     SDPSource source, GstWebRTCSDPType type, GError ** error)
49 {
50 #define STATE(val) GST_WEBRTC_SIGNALING_STATE_ ## val
51 #define TYPE(val) GST_WEBRTC_SDP_TYPE_ ## val
52
53   if (source == SDP_LOCAL && type == TYPE (OFFER) && state == STATE (STABLE))
54     return TRUE;
55   if (source == SDP_LOCAL && type == TYPE (OFFER)
56       && state == STATE (HAVE_LOCAL_OFFER))
57     return TRUE;
58   if (source == SDP_LOCAL && type == TYPE (ANSWER)
59       && state == STATE (HAVE_REMOTE_OFFER))
60     return TRUE;
61   if (source == SDP_LOCAL && type == TYPE (PRANSWER)
62       && state == STATE (HAVE_REMOTE_OFFER))
63     return TRUE;
64   if (source == SDP_LOCAL && type == TYPE (PRANSWER)
65       && state == STATE (HAVE_LOCAL_PRANSWER))
66     return TRUE;
67
68   if (source == SDP_REMOTE && type == TYPE (OFFER) && state == STATE (STABLE))
69     return TRUE;
70   if (source == SDP_REMOTE && type == TYPE (OFFER)
71       && state == STATE (HAVE_REMOTE_OFFER))
72     return TRUE;
73   if (source == SDP_REMOTE && type == TYPE (ANSWER)
74       && state == STATE (HAVE_LOCAL_OFFER))
75     return TRUE;
76   if (source == SDP_REMOTE && type == TYPE (PRANSWER)
77       && state == STATE (HAVE_LOCAL_OFFER))
78     return TRUE;
79   if (source == SDP_REMOTE && type == TYPE (PRANSWER)
80       && state == STATE (HAVE_REMOTE_PRANSWER))
81     return TRUE;
82
83   {
84     gchar *state_str = _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
85         state);
86     gchar *type_str = _enum_value_to_string (GST_TYPE_WEBRTC_SDP_TYPE, type);
87     g_set_error (error, GST_WEBRTC_BIN_ERROR,
88         GST_WEBRTC_BIN_ERROR_INVALID_STATE,
89         "Not in the correct state (%s) for setting %s %s description",
90         state_str, _sdp_source_to_string (source), type_str);
91     g_free (state_str);
92     g_free (type_str);
93   }
94
95   return FALSE;
96
97 #undef STATE
98 #undef TYPE
99 }
100
101 static gboolean
102 _check_sdp_crypto (SDPSource source, GstWebRTCSessionDescription * sdp,
103     GError ** error)
104 {
105   const gchar *message_fingerprint, *fingerprint;
106   const GstSDPKey *key;
107   int i;
108
109   key = gst_sdp_message_get_key (sdp->sdp);
110   if (!IS_EMPTY_SDP_ATTRIBUTE (key->data)) {
111     g_set_error_literal (error, GST_WEBRTC_BIN_ERROR,
112         GST_WEBRTC_BIN_ERROR_BAD_SDP, "sdp contains a k line");
113     return FALSE;
114   }
115
116   message_fingerprint = fingerprint =
117       gst_sdp_message_get_attribute_val (sdp->sdp, "fingerprint");
118   for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
119     const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i);
120     const gchar *media_fingerprint =
121         gst_sdp_media_get_attribute_val (media, "fingerprint");
122
123     if (!IS_EMPTY_SDP_ATTRIBUTE (message_fingerprint)
124         && !IS_EMPTY_SDP_ATTRIBUTE (media_fingerprint)) {
125       g_set_error (error, GST_WEBRTC_BIN_ERROR,
126           GST_WEBRTC_BIN_ERROR_FINGERPRINT,
127           "No fingerprint lines in sdp for media %u", i);
128       return FALSE;
129     }
130     if (IS_EMPTY_SDP_ATTRIBUTE (fingerprint)) {
131       fingerprint = media_fingerprint;
132     }
133     if (!IS_EMPTY_SDP_ATTRIBUTE (media_fingerprint)
134         && g_strcmp0 (fingerprint, media_fingerprint) != 0) {
135       g_set_error (error, GST_WEBRTC_BIN_ERROR,
136           GST_WEBRTC_BIN_ERROR_FINGERPRINT,
137           "Fingerprint in media %u differs from %s fingerprint. "
138           "\'%s\' != \'%s\'", i, message_fingerprint ? "global" : "previous",
139           fingerprint, media_fingerprint);
140       return FALSE;
141     }
142   }
143
144   return TRUE;
145 }
146
147 #if 0
148 static gboolean
149 _session_has_attribute_key (const GstSDPMessage * msg, const gchar * key)
150 {
151   int i;
152   for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
153     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
154
155     if (g_strcmp0 (attr->key, key) == 0)
156       return TRUE;
157   }
158
159   return FALSE;
160 }
161
162 static gboolean
163 _session_has_attribute_key_value (const GstSDPMessage * msg, const gchar * key,
164     const gchar * value)
165 {
166   int i;
167   for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
168     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
169
170     if (g_strcmp0 (attr->key, key) == 0 && g_strcmp0 (attr->value, value) == 0)
171       return TRUE;
172   }
173
174   return FALSE;
175 }
176
177 static gboolean
178 _check_trickle_ice (GstSDPMessage * msg, GError ** error)
179 {
180   if (!_session_has_attribute_key_value (msg, "ice-options", "trickle")) {
181     g_set_error_literal (error, GST_WEBRTC_BIN_ERROR,
182         GST_WEBRTC_BIN_ERROR_BAD_SDP,
183         "No required \'a=ice-options:trickle\' line in sdp");
184   }
185   return TRUE;
186 }
187 #endif
188 gboolean
189 _media_has_attribute_key (const GstSDPMedia * media, const gchar * key)
190 {
191   int i;
192   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
193     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
194
195     if (g_strcmp0 (attr->key, key) == 0)
196       return TRUE;
197   }
198
199   return FALSE;
200 }
201
202 static gboolean
203 _media_has_mid (const GstSDPMedia * media, guint media_idx, GError ** error)
204 {
205   const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
206   if (IS_EMPTY_SDP_ATTRIBUTE (mid)) {
207     g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
208         "media %u is missing or contains an empty \'mid\' attribute",
209         media_idx);
210     return FALSE;
211   }
212   return TRUE;
213 }
214
215 const gchar *
216 _media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
217 {
218   const gchar *ice_ufrag;
219
220   ice_ufrag = gst_sdp_message_get_attribute_val (msg, "ice-ufrag");
221   if (IS_EMPTY_SDP_ATTRIBUTE (ice_ufrag)) {
222     const GstSDPMedia *media = gst_sdp_message_get_media (msg, media_idx);
223     ice_ufrag = gst_sdp_media_get_attribute_val (media, "ice-ufrag");
224     if (IS_EMPTY_SDP_ATTRIBUTE (ice_ufrag))
225       return NULL;
226   }
227   return ice_ufrag;
228 }
229
230 const gchar *
231 _media_get_ice_pwd (const GstSDPMessage * msg, guint media_idx)
232 {
233   const gchar *ice_pwd;
234
235   ice_pwd = gst_sdp_message_get_attribute_val (msg, "ice-pwd");
236   if (IS_EMPTY_SDP_ATTRIBUTE (ice_pwd)) {
237     const GstSDPMedia *media = gst_sdp_message_get_media (msg, media_idx);
238     ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
239     if (IS_EMPTY_SDP_ATTRIBUTE (ice_pwd))
240       return NULL;
241   }
242   return ice_pwd;
243 }
244
245 static gboolean
246 _media_has_setup (const GstSDPMedia * media, guint media_idx, GError ** error)
247 {
248   static const gchar *valid_setups[] = { "actpass", "active", "passive", NULL };
249   const gchar *setup = gst_sdp_media_get_attribute_val (media, "setup");
250   if (IS_EMPTY_SDP_ATTRIBUTE (setup)) {
251     g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
252         "media %u is missing or contains an empty \'setup\' attribute",
253         media_idx);
254     return FALSE;
255   }
256   if (!g_strv_contains (valid_setups, setup)) {
257     g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
258         "media %u contains unknown \'setup\' attribute, \'%s\'", media_idx,
259         setup);
260     return FALSE;
261   }
262   return TRUE;
263 }
264
265 #if 0
266 static gboolean
267 _media_has_dtls_id (const GstSDPMedia * media, guint media_idx, GError ** error)
268 {
269   const gchar *dtls_id = gst_sdp_media_get_attribute_val (media, "ice-pwd");
270   if (IS_EMPTY_SDP_ATTRIBUTE (dtls_id)) {
271     g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
272         "media %u is missing or contains an empty \'dtls-id\' attribute",
273         media_idx);
274     return FALSE;
275   }
276   return TRUE;
277 }
278 #endif
279 gboolean
280 validate_sdp (GstWebRTCSignalingState state, SDPSource source,
281     GstWebRTCSessionDescription * sdp, GError ** error)
282 {
283   const gchar *group, *bundle_ice_ufrag = NULL, *bundle_ice_pwd = NULL;
284   gchar **group_members = NULL;
285   gboolean is_bundle = FALSE;
286   int i;
287
288   if (!_check_valid_state_for_sdp_change (state, source, sdp->type, error))
289     return FALSE;
290   if (!_check_sdp_crypto (source, sdp, error))
291     return FALSE;
292 /* not explicitly required
293   if (ICE && !_check_trickle_ice (sdp->sdp))
294     return FALSE;*/
295   group = gst_sdp_message_get_attribute_val (sdp->sdp, "group");
296   is_bundle = group && g_str_has_prefix (group, "BUNDLE");
297   if (is_bundle)
298     group_members = g_strsplit (&group[6], " ", -1);
299
300   for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
301     const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i);
302     const gchar *mid;
303     gboolean media_in_bundle = FALSE;
304     if (!_media_has_mid (media, i, error))
305       goto fail;
306     mid = gst_sdp_media_get_attribute_val (media, "mid");
307     media_in_bundle = is_bundle
308         && g_strv_contains ((const gchar **) group_members, mid);
309     if (!_media_get_ice_ufrag (sdp->sdp, i)) {
310       g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
311           "media %u is missing or contains an empty \'ice-ufrag\' attribute",
312           i);
313       goto fail;
314     }
315     if (!_media_get_ice_pwd (sdp->sdp, i)) {
316       g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
317           "media %u is missing or contains an empty \'ice-pwd\' attribute", i);
318       goto fail;
319     }
320     if (!_media_has_setup (media, i, error))
321       goto fail;
322     /* check parameters in bundle are the same */
323     if (media_in_bundle) {
324       const gchar *ice_ufrag =
325           gst_sdp_media_get_attribute_val (media, "ice-ufrag");
326       const gchar *ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
327       if (!bundle_ice_ufrag)
328         bundle_ice_ufrag = ice_ufrag;
329       else if (g_strcmp0 (bundle_ice_ufrag, ice_ufrag) != 0) {
330         g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
331             "media %u has different ice-ufrag values in bundle. "
332             "%s != %s", i, bundle_ice_ufrag, ice_ufrag);
333         goto fail;
334       }
335       if (!bundle_ice_pwd) {
336         bundle_ice_pwd = ice_pwd;
337       } else if (g_strcmp0 (bundle_ice_pwd, ice_pwd) != 0) {
338         g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
339             "media %u has different ice-pwd values in bundle. "
340             "%s != %s", i, bundle_ice_pwd, ice_pwd);
341         goto fail;
342       }
343     }
344   }
345
346   g_strfreev (group_members);
347
348   return TRUE;
349
350 fail:
351   g_strfreev (group_members);
352   return FALSE;
353 }
354
355 GstWebRTCRTPTransceiverDirection
356 _get_direction_from_media (const GstSDPMedia * media)
357 {
358   GstWebRTCRTPTransceiverDirection new_dir =
359       GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
360   int i;
361
362   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
363     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
364
365     if (g_strcmp0 (attr->key, "sendonly") == 0) {
366       if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
367         GST_ERROR ("Multiple direction attributes");
368         return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
369       }
370       new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDONLY;
371     } else if (g_strcmp0 (attr->key, "sendrecv") == 0) {
372       if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
373         GST_ERROR ("Multiple direction attributes");
374         return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
375       }
376       new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_SENDRECV;
377     } else if (g_strcmp0 (attr->key, "recvonly") == 0) {
378       if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
379         GST_ERROR ("Multiple direction attributes");
380         return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
381       }
382       new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_RECVONLY;
383     } else if (g_strcmp0 (attr->key, "inactive") == 0) {
384       if (new_dir != GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE) {
385         GST_ERROR ("Multiple direction attributes");
386         return GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
387       }
388       new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE;
389     }
390   }
391
392   return new_dir;
393 }
394
395 #define DIR(val) GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_ ## val
396 GstWebRTCRTPTransceiverDirection
397 _intersect_answer_directions (GstWebRTCRTPTransceiverDirection offer,
398     GstWebRTCRTPTransceiverDirection answer)
399 {
400   if (offer == DIR (INACTIVE) || answer == DIR (INACTIVE))
401     return DIR (INACTIVE);
402   if (offer == DIR (SENDONLY) && answer == DIR (SENDRECV))
403     return DIR (RECVONLY);
404   if (offer == DIR (SENDONLY) && answer == DIR (RECVONLY))
405     return DIR (RECVONLY);
406   if (offer == DIR (RECVONLY) && answer == DIR (SENDRECV))
407     return DIR (SENDONLY);
408   if (offer == DIR (RECVONLY) && answer == DIR (SENDONLY))
409     return DIR (SENDONLY);
410   if (offer == DIR (SENDRECV) && answer == DIR (SENDRECV))
411     return DIR (SENDRECV);
412   if (offer == DIR (SENDRECV) && answer == DIR (SENDONLY))
413     return DIR (SENDONLY);
414   if (offer == DIR (SENDRECV) && answer == DIR (RECVONLY))
415     return DIR (RECVONLY);
416   if (offer == DIR (RECVONLY) && answer == DIR (RECVONLY))
417     return DIR (INACTIVE);
418   if (offer == DIR (SENDONLY) && answer == DIR (SENDONLY))
419     return DIR (INACTIVE);
420
421   return DIR (NONE);
422 }
423
424 void
425 _media_replace_direction (GstSDPMedia * media,
426     GstWebRTCRTPTransceiverDirection direction)
427 {
428   gchar *dir_str;
429   int i;
430
431   dir_str =
432       _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
433       direction);
434
435   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
436     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
437
438     if (g_strcmp0 (attr->key, "sendonly") == 0
439         || g_strcmp0 (attr->key, "sendrecv") == 0
440         || g_strcmp0 (attr->key, "recvonly") == 0
441         || g_strcmp0 (attr->key, "inactive") == 0) {
442       GstSDPAttribute new_attr = { 0, };
443       GST_TRACE ("replace %s with %s", attr->key, dir_str);
444       gst_sdp_attribute_set (&new_attr, dir_str, "");
445       gst_sdp_media_replace_attribute (media, i, &new_attr);
446       g_free (dir_str);
447       return;
448     }
449   }
450
451   GST_TRACE ("add %s", dir_str);
452   gst_sdp_media_add_attribute (media, dir_str, "");
453   g_free (dir_str);
454 }
455
456 GstWebRTCRTPTransceiverDirection
457 _get_final_direction (GstWebRTCRTPTransceiverDirection local_dir,
458     GstWebRTCRTPTransceiverDirection remote_dir)
459 {
460   GstWebRTCRTPTransceiverDirection new_dir;
461   new_dir = DIR (NONE);
462   switch (local_dir) {
463     case DIR (INACTIVE):
464       new_dir = DIR (INACTIVE);
465       break;
466     case DIR (SENDONLY):
467       if (remote_dir == DIR (SENDONLY)) {
468         GST_ERROR ("remote SDP has the same directionality. "
469             "This is not legal.");
470         return DIR (NONE);
471       } else if (remote_dir == DIR (INACTIVE)) {
472         new_dir = DIR (INACTIVE);
473       } else {
474         new_dir = DIR (SENDONLY);
475       }
476       break;
477     case DIR (RECVONLY):
478       if (remote_dir == DIR (RECVONLY)) {
479         GST_ERROR ("remote SDP has the same directionality. "
480             "This is not legal.");
481         return DIR (NONE);
482       } else if (remote_dir == DIR (INACTIVE)) {
483         new_dir = DIR (INACTIVE);
484       } else {
485         new_dir = DIR (RECVONLY);
486       }
487       break;
488     case DIR (SENDRECV):
489       if (remote_dir == DIR (INACTIVE)) {
490         new_dir = DIR (INACTIVE);
491       } else if (remote_dir == DIR (SENDONLY)) {
492         new_dir = DIR (RECVONLY);
493       } else if (remote_dir == DIR (RECVONLY)) {
494         new_dir = DIR (SENDONLY);
495       } else if (remote_dir == DIR (SENDRECV)) {
496         new_dir = DIR (SENDRECV);
497       }
498       break;
499     default:
500       g_assert_not_reached ();
501       break;
502   }
503
504   if (new_dir == DIR (NONE)) {
505     GST_ERROR ("Abnormal situation!");
506     return DIR (NONE);
507   }
508
509   return new_dir;
510 }
511
512 #undef DIR
513
514 #define SETUP(val) GST_WEBRTC_DTLS_SETUP_ ## val
515 GstWebRTCDTLSSetup
516 _get_dtls_setup_from_media (const GstSDPMedia * media)
517 {
518   int i;
519
520   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
521     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
522
523     if (g_strcmp0 (attr->key, "setup") == 0) {
524       if (g_strcmp0 (attr->value, "actpass") == 0) {
525         return SETUP (ACTPASS);
526       } else if (g_strcmp0 (attr->value, "active") == 0) {
527         return SETUP (ACTIVE);
528       } else if (g_strcmp0 (attr->value, "passive") == 0) {
529         return SETUP (PASSIVE);
530       } else {
531         GST_ERROR ("unknown setup value %s", attr->value);
532         return SETUP (NONE);
533       }
534     }
535   }
536
537   GST_LOG ("no setup attribute in media");
538   return SETUP (NONE);
539 }
540
541 GstWebRTCDTLSSetup
542 _intersect_dtls_setup (GstWebRTCDTLSSetup offer)
543 {
544   switch (offer) {
545     case SETUP (NONE):         /* default is active */
546     case SETUP (ACTPASS):
547     case SETUP (PASSIVE):
548       return SETUP (ACTIVE);
549     case SETUP (ACTIVE):
550       return SETUP (PASSIVE);
551     default:
552       return SETUP (NONE);
553   }
554 }
555
556 void
557 _media_replace_setup (GstSDPMedia * media, GstWebRTCDTLSSetup setup)
558 {
559   gchar *setup_str;
560   int i;
561
562   setup_str = _enum_value_to_string (GST_TYPE_WEBRTC_DTLS_SETUP, setup);
563
564   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
565     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
566
567     if (g_strcmp0 (attr->key, "setup") == 0) {
568       GstSDPAttribute new_attr = { 0, };
569       GST_TRACE ("replace setup:%s with setup:%s", attr->value, setup_str);
570       gst_sdp_attribute_set (&new_attr, "setup", setup_str);
571       gst_sdp_media_replace_attribute (media, i, &new_attr);
572       return;
573     }
574   }
575
576   GST_TRACE ("add setup:%s", setup_str);
577   gst_sdp_media_add_attribute (media, "setup", setup_str);
578   g_free (setup_str);
579 }
580
581 GstWebRTCDTLSSetup
582 _get_final_setup (GstWebRTCDTLSSetup local_setup,
583     GstWebRTCDTLSSetup remote_setup)
584 {
585   GstWebRTCDTLSSetup new_setup;
586
587   new_setup = SETUP (NONE);
588   switch (local_setup) {
589     case SETUP (NONE):
590       /* someone's done a bad job of mangling the SDP. or bugs */
591       g_critical ("Received a locally generated sdp without a parseable "
592           "\'a=setup\' line.  This indicates a bug somewhere.  Bailing");
593       return SETUP (NONE);
594     case SETUP (ACTIVE):
595       if (remote_setup == SETUP (ACTIVE)) {
596         GST_ERROR ("remote SDP has the same "
597             "\'a=setup:active\' attribute. This is not legal");
598         return SETUP (NONE);
599       }
600       new_setup = SETUP (ACTIVE);
601       break;
602     case SETUP (PASSIVE):
603       if (remote_setup == SETUP (PASSIVE)) {
604         GST_ERROR ("remote SDP has the same "
605             "\'a=setup:passive\' attribute. This is not legal");
606         return SETUP (NONE);
607       }
608       new_setup = SETUP (PASSIVE);
609       break;
610     case SETUP (ACTPASS):
611       if (remote_setup == SETUP (ACTPASS)) {
612         GST_ERROR ("remote SDP has the same "
613             "\'a=setup:actpass\' attribute. This is not legal");
614         return SETUP (NONE);
615       }
616       if (remote_setup == SETUP (ACTIVE))
617         new_setup = SETUP (PASSIVE);
618       else if (remote_setup == SETUP (PASSIVE))
619         new_setup = SETUP (ACTIVE);
620       else if (remote_setup == SETUP (NONE)) {
621         /* XXX: what to do here? */
622         GST_WARNING ("unspecified situation. local: "
623             "\'a=setup:actpass\' remote: none/unparseable");
624         new_setup = SETUP (ACTIVE);
625       }
626       break;
627     default:
628       g_assert_not_reached ();
629       return SETUP (NONE);
630   }
631   if (new_setup == SETUP (NONE)) {
632     GST_ERROR ("Abnormal situation!");
633     return SETUP (NONE);
634   }
635
636   return new_setup;
637 }
638
639 #undef SETUP
640
641 gchar *
642 _generate_fingerprint_from_certificate (gchar * certificate,
643     GChecksumType checksum_type)
644 {
645   gchar **lines, *line;
646   guchar *tmp, *decoded, *digest;
647   GChecksum *checksum;
648   GString *fingerprint;
649   gsize decoded_length, digest_size;
650   gint state = 0;
651   guint save = 0;
652   int i;
653
654   g_return_val_if_fail (certificate != NULL, NULL);
655
656   /* 1. decode the certificate removing newlines and the certificate header
657    * and footer */
658   decoded = tmp = g_new0 (guchar, (strlen (certificate) / 4) * 3 + 3);
659   lines = g_strsplit (certificate, "\n", 0);
660   for (i = 0, line = lines[i]; line; line = lines[++i]) {
661     if (line[0] && !g_str_has_prefix (line, "-----"))
662       tmp += g_base64_decode_step (line, strlen (line), tmp, &state, &save);
663   }
664   g_strfreev (lines);
665   decoded_length = tmp - decoded;
666
667   /* 2. compute a checksum of the decoded certificate */
668   checksum = g_checksum_new (checksum_type);
669   digest_size = g_checksum_type_get_length (checksum_type);
670   digest = g_new (guint8, digest_size);
671   g_checksum_update (checksum, decoded, decoded_length);
672   g_checksum_get_digest (checksum, digest, &digest_size);
673   g_free (decoded);
674
675   /* 3. hex encode the checksum separated with ':'s */
676   fingerprint = g_string_new (NULL);
677   for (i = 0; i < digest_size; i++) {
678     if (i)
679       g_string_append (fingerprint, ":");
680     g_string_append_printf (fingerprint, "%02X", digest[i]);
681   }
682
683   g_free (digest);
684   g_checksum_free (checksum);
685
686   return g_string_free (fingerprint, FALSE);
687 }
688
689 #define DEFAULT_ICE_UFRAG_LEN 32
690 #define DEFAULT_ICE_PASSWORD_LEN 32
691 static const gchar *ice_credential_chars =
692     "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/";
693
694 void
695 _generate_ice_credentials (gchar ** ufrag, gchar ** password)
696 {
697   int i;
698
699   *ufrag = g_malloc0 (DEFAULT_ICE_UFRAG_LEN + 1);
700   for (i = 0; i < DEFAULT_ICE_UFRAG_LEN; i++)
701     (*ufrag)[i] =
702         ice_credential_chars[g_random_int_range (0,
703             strlen (ice_credential_chars))];
704
705   *password = g_malloc0 (DEFAULT_ICE_PASSWORD_LEN + 1);
706   for (i = 0; i < DEFAULT_ICE_PASSWORD_LEN; i++)
707     (*password)[i] =
708         ice_credential_chars[g_random_int_range (0,
709             strlen (ice_credential_chars))];
710 }
711
712 int
713 _get_sctp_port_from_media (const GstSDPMedia * media)
714 {
715   int i;
716   const gchar *format;
717   gchar *endptr;
718
719   if (gst_sdp_media_formats_len (media) != 1) {
720     /* only exactly one format is supported */
721     return -1;
722   }
723
724   format = gst_sdp_media_get_format (media, 0);
725
726   if (g_strcmp0 (format, "webrtc-datachannel") == 0) {
727     /* draft-ietf-mmusic-sctp-sdp-21, e.g. Firefox 63 and later */
728
729     for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
730       const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
731
732       if (g_strcmp0 (attr->key, "sctp-port") == 0) {
733         gint64 port = g_ascii_strtoll (attr->value, &endptr, 10);
734         if (endptr == attr->value) {
735           /* conversion error */
736           return -1;
737         }
738         return port;
739       }
740     }
741   } else {
742     /* draft-ietf-mmusic-sctp-sdp-05, e.g. Chrome as recent as 75 */
743     gint64 port = g_ascii_strtoll (format, &endptr, 10);
744     if (endptr == format) {
745       /* conversion error */
746       return -1;
747     }
748
749     for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
750       const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
751
752       if (g_strcmp0 (attr->key, "sctpmap") == 0 && atoi (attr->value) == port) {
753         /* a=sctpmap:5000 webrtc-datachannel 256 */
754         gchar **parts = g_strsplit (attr->value, " ", 3);
755         if (!parts[1] || g_strcmp0 (parts[1], "webrtc-datachannel") != 0) {
756           port = -1;
757         }
758         g_strfreev (parts);
759         return port;
760       }
761     }
762   }
763
764   return -1;
765 }
766
767 guint64
768 _get_sctp_max_message_size_from_media (const GstSDPMedia * media)
769 {
770   int i;
771
772   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
773     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
774
775     if (g_strcmp0 (attr->key, "max-message-size") == 0)
776       return atoi (attr->value);
777   }
778
779   return 65536;
780 }
781
782 gboolean
783 _message_media_is_datachannel (const GstSDPMessage * msg, guint media_id)
784 {
785   const GstSDPMedia *media;
786
787   if (!msg)
788     return FALSE;
789
790   if (gst_sdp_message_medias_len (msg) <= media_id)
791     return FALSE;
792
793   media = gst_sdp_message_get_media (msg, media_id);
794
795   if (g_strcmp0 (gst_sdp_media_get_media (media), "application") != 0)
796     return FALSE;
797
798   if (gst_sdp_media_formats_len (media) != 1)
799     return FALSE;
800
801   if (g_strcmp0 (gst_sdp_media_get_format (media, 0),
802           "webrtc-datachannel") != 0)
803     return FALSE;
804
805   return TRUE;
806 }
807
808 guint
809 _message_get_datachannel_index (const GstSDPMessage * msg)
810 {
811   guint i;
812
813   for (i = 0; i < gst_sdp_message_medias_len (msg); i++) {
814     if (_message_media_is_datachannel (msg, i)) {
815       g_assert (i < G_MAXUINT);
816       return i;
817     }
818   }
819
820   return G_MAXUINT;
821 }
822
823 void
824 _get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
825     gchar ** ufrag, gchar ** pwd)
826 {
827   int i;
828
829   *ufrag = NULL;
830   *pwd = NULL;
831
832   {
833     /* search in the corresponding media section */
834     const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx);
835     const gchar *tmp_ufrag =
836         gst_sdp_media_get_attribute_val (media, "ice-ufrag");
837     const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
838     if (tmp_ufrag && tmp_pwd) {
839       *ufrag = g_strdup (tmp_ufrag);
840       *pwd = g_strdup (tmp_pwd);
841       return;
842     }
843   }
844
845   /* then in the sdp message itself */
846   for (i = 0; i < gst_sdp_message_attributes_len (sdp); i++) {
847     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (sdp, i);
848
849     if (g_strcmp0 (attr->key, "ice-ufrag") == 0) {
850       g_assert (!*ufrag);
851       *ufrag = g_strdup (attr->value);
852     } else if (g_strcmp0 (attr->key, "ice-pwd") == 0) {
853       g_assert (!*pwd);
854       *pwd = g_strdup (attr->value);
855     }
856   }
857   if (!*ufrag && !*pwd) {
858     /* Check in the medias themselves. According to JSEP, they should be
859      * identical FIXME: only for bundle-d streams */
860     for (i = 0; i < gst_sdp_message_medias_len (sdp); i++) {
861       const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
862       const gchar *tmp_ufrag =
863           gst_sdp_media_get_attribute_val (media, "ice-ufrag");
864       const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
865       if (tmp_ufrag && tmp_pwd) {
866         *ufrag = g_strdup (tmp_ufrag);
867         *pwd = g_strdup (tmp_pwd);
868         break;
869       }
870     }
871   }
872 }
873
874 gboolean
875 _parse_bundle (GstSDPMessage * sdp, GStrv * bundled)
876 {
877   const gchar *group;
878   gboolean ret = FALSE;
879
880   group = gst_sdp_message_get_attribute_val (sdp, "group");
881
882   if (group && g_str_has_prefix (group, "BUNDLE ")) {
883     *bundled = g_strsplit (group + strlen ("BUNDLE "), " ", 0);
884
885     if (!(*bundled)[0]) {
886       GST_ERROR ("Invalid format for BUNDLE group, expected at least "
887           "one mid (%s)", group);
888       g_strfreev (*bundled);
889       *bundled = NULL;
890       goto done;
891     }
892   } else {
893     ret = TRUE;
894     goto done;
895   }
896
897   ret = TRUE;
898
899 done:
900   return ret;
901 }
902
903 gboolean
904 _get_bundle_index (GstSDPMessage * sdp, GStrv bundled, guint * idx)
905 {
906   gboolean ret = FALSE;
907   guint i;
908
909   for (i = 0; i < gst_sdp_message_medias_len (sdp); i++) {
910     const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
911     const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
912
913     if (!g_strcmp0 (mid, bundled[0])) {
914       *idx = i;
915       ret = TRUE;
916       break;
917     }
918   }
919
920   return ret;
921 }