webrtcsdp: Remove comparison between media and session fingerprint
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / 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     const gchar *state_str =
85         _enum_value_to_string (GST_TYPE_WEBRTC_SIGNALING_STATE,
86         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);
92   }
93
94   return FALSE;
95
96 #undef STATE
97 #undef TYPE
98 }
99
100 static gboolean
101 _check_sdp_crypto (SDPSource source, GstWebRTCSessionDescription * sdp,
102     GError ** error)
103 {
104   const gchar *message_fingerprint;
105   const GstSDPKey *key;
106   int i;
107
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");
112     return FALSE;
113   }
114
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");
121
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);
127       return FALSE;
128     }
129   }
130
131   return TRUE;
132 }
133
134 gboolean
135 _message_has_attribute_key (const GstSDPMessage * msg, const gchar * key)
136 {
137   int i;
138   for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
139     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
140
141     if (g_strcmp0 (attr->key, key) == 0)
142       return TRUE;
143   }
144
145   return FALSE;
146 }
147
148 #if 0
149 static gboolean
150 _session_has_attribute_key_value (const GstSDPMessage * msg, const gchar * key,
151     const gchar * value)
152 {
153   int i;
154   for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
155     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
156
157     if (g_strcmp0 (attr->key, key) == 0 && g_strcmp0 (attr->value, value) == 0)
158       return TRUE;
159   }
160
161   return FALSE;
162 }
163
164 static gboolean
165 _check_trickle_ice (GstSDPMessage * msg, GError ** error)
166 {
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");
171   }
172   return TRUE;
173 }
174 #endif
175 gboolean
176 _media_has_attribute_key (const GstSDPMedia * media, const gchar * key)
177 {
178   int i;
179   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
180     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
181
182     if (g_strcmp0 (attr->key, key) == 0)
183       return TRUE;
184   }
185
186   return FALSE;
187 }
188
189 static gboolean
190 _media_has_mid (const GstSDPMedia * media, guint media_idx, GError ** error)
191 {
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",
196         media_idx);
197     return FALSE;
198   }
199   return TRUE;
200 }
201
202 const gchar *
203 _media_get_ice_ufrag (const GstSDPMessage * msg, guint media_idx)
204 {
205   const gchar *ice_ufrag;
206
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))
212       return NULL;
213   }
214   return ice_ufrag;
215 }
216
217 const gchar *
218 _media_get_ice_pwd (const GstSDPMessage * msg, guint media_idx)
219 {
220   const gchar *ice_pwd;
221
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))
227       return NULL;
228   }
229   return ice_pwd;
230 }
231
232 static gboolean
233 _media_has_setup (const GstSDPMedia * media, guint media_idx, GError ** error)
234 {
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",
240         media_idx);
241     return FALSE;
242   }
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,
246         setup);
247     return FALSE;
248   }
249   return TRUE;
250 }
251
252 #if 0
253 static gboolean
254 _media_has_dtls_id (const GstSDPMedia * media, guint media_idx, GError ** error)
255 {
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",
260         media_idx);
261     return FALSE;
262   }
263   return TRUE;
264 }
265 #endif
266 gboolean
267 validate_sdp (GstWebRTCSignalingState state, SDPSource source,
268     GstWebRTCSessionDescription * sdp, GError ** error)
269 {
270   const gchar *group, *bundle_ice_ufrag = NULL, *bundle_ice_pwd = NULL;
271   gchar **group_members = NULL;
272   gboolean is_bundle = FALSE;
273   int i;
274
275   if (!_check_valid_state_for_sdp_change (state, source, sdp->type, error))
276     return FALSE;
277   if (!_check_sdp_crypto (source, sdp, error))
278     return FALSE;
279 /* not explicitly required
280   if (ICE && !_check_trickle_ice (sdp->sdp))
281     return FALSE;*/
282   group = gst_sdp_message_get_attribute_val (sdp->sdp, "group");
283   is_bundle = group && g_str_has_prefix (group, "BUNDLE");
284   if (is_bundle)
285     group_members = g_strsplit (&group[6], " ", -1);
286
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);
289     const gchar *mid;
290     gboolean media_in_bundle = FALSE;
291     if (!_media_has_mid (media, i, error))
292       goto fail;
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",
299           i);
300       goto fail;
301     }
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);
305       goto fail;
306     }
307     if (!_media_has_setup (media, i, error))
308       goto fail;
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);
320         goto fail;
321       }
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);
328         goto fail;
329       }
330     }
331   }
332
333   g_strfreev (group_members);
334
335   return TRUE;
336
337 fail:
338   g_strfreev (group_members);
339   return FALSE;
340 }
341
342 GstWebRTCRTPTransceiverDirection
343 _get_direction_from_media (const GstSDPMedia * media)
344 {
345   GstWebRTCRTPTransceiverDirection new_dir =
346       GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_NONE;
347   int i;
348
349   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
350     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
351
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;
356       }
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;
362       }
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;
368       }
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;
374       }
375       new_dir = GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_INACTIVE;
376     }
377   }
378
379   return new_dir;
380 }
381
382 #define DIR(val) GST_WEBRTC_RTP_TRANSCEIVER_DIRECTION_ ## val
383 GstWebRTCRTPTransceiverDirection
384 _intersect_answer_directions (GstWebRTCRTPTransceiverDirection offer,
385     GstWebRTCRTPTransceiverDirection answer)
386 {
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);
407
408   return DIR (NONE);
409 }
410
411 void
412 _media_replace_direction (GstSDPMedia * media,
413     GstWebRTCRTPTransceiverDirection direction)
414 {
415   const gchar *dir_str;
416   int i;
417
418   dir_str = gst_webrtc_rtp_transceiver_direction_to_string (direction);
419
420   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
421     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
422
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);
431       return;
432     }
433   }
434
435   GST_TRACE ("add %s", dir_str);
436   gst_sdp_media_add_attribute (media, dir_str, "");
437 }
438
439 GstWebRTCRTPTransceiverDirection
440 _get_final_direction (GstWebRTCRTPTransceiverDirection local_dir,
441     GstWebRTCRTPTransceiverDirection remote_dir)
442 {
443   GstWebRTCRTPTransceiverDirection new_dir;
444   new_dir = DIR (NONE);
445   switch (local_dir) {
446     case DIR (INACTIVE):
447       new_dir = DIR (INACTIVE);
448       break;
449     case DIR (SENDONLY):
450       if (remote_dir == DIR (SENDONLY)) {
451         GST_ERROR ("remote SDP has the same directionality. "
452             "This is not legal.");
453         return DIR (NONE);
454       } else if (remote_dir == DIR (INACTIVE)) {
455         new_dir = DIR (INACTIVE);
456       } else {
457         new_dir = DIR (SENDONLY);
458       }
459       break;
460     case DIR (RECVONLY):
461       if (remote_dir == DIR (RECVONLY)) {
462         GST_ERROR ("remote SDP has the same directionality. "
463             "This is not legal.");
464         return DIR (NONE);
465       } else if (remote_dir == DIR (INACTIVE)) {
466         new_dir = DIR (INACTIVE);
467       } else {
468         new_dir = DIR (RECVONLY);
469       }
470       break;
471     case DIR (SENDRECV):
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);
480       }
481       break;
482     default:
483       g_assert_not_reached ();
484       break;
485   }
486
487   if (new_dir == DIR (NONE)) {
488     GST_ERROR ("Abnormal situation!");
489     return DIR (NONE);
490   }
491
492   return new_dir;
493 }
494
495 #undef DIR
496
497 #define SETUP(val) GST_WEBRTC_DTLS_SETUP_ ## val
498 GstWebRTCDTLSSetup
499 _get_dtls_setup_from_media (const GstSDPMedia * media)
500 {
501   int i;
502
503   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
504     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
505
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);
513       } else {
514         GST_ERROR ("unknown setup value %s", attr->value);
515         return SETUP (NONE);
516       }
517     }
518   }
519
520   GST_LOG ("no setup attribute in media");
521   return SETUP (NONE);
522 }
523
524 GstWebRTCDTLSSetup
525 _intersect_dtls_setup (GstWebRTCDTLSSetup offer)
526 {
527   switch (offer) {
528     case SETUP (NONE):         /* default is active */
529     case SETUP (ACTPASS):
530     case SETUP (PASSIVE):
531       return SETUP (ACTIVE);
532     case SETUP (ACTIVE):
533       return SETUP (PASSIVE);
534     default:
535       return SETUP (NONE);
536   }
537 }
538
539 void
540 _media_replace_setup (GstSDPMedia * media, GstWebRTCDTLSSetup setup)
541 {
542   const gchar *setup_str;
543   int i;
544
545   setup_str = _enum_value_to_string (GST_TYPE_WEBRTC_DTLS_SETUP, setup);
546
547   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
548     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
549
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);
555       return;
556     }
557   }
558
559   GST_TRACE ("add setup:%s", setup_str);
560   gst_sdp_media_add_attribute (media, "setup", setup_str);
561 }
562
563 GstWebRTCDTLSSetup
564 _get_final_setup (GstWebRTCDTLSSetup local_setup,
565     GstWebRTCDTLSSetup remote_setup)
566 {
567   GstWebRTCDTLSSetup new_setup;
568
569   new_setup = SETUP (NONE);
570   switch (local_setup) {
571     case SETUP (NONE):
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");
575       return SETUP (NONE);
576     case SETUP (ACTIVE):
577       if (remote_setup == SETUP (ACTIVE)) {
578         GST_ERROR ("remote SDP has the same "
579             "\'a=setup:active\' attribute. This is not legal");
580         return SETUP (NONE);
581       }
582       new_setup = SETUP (ACTIVE);
583       break;
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");
588         return SETUP (NONE);
589       }
590       new_setup = SETUP (PASSIVE);
591       break;
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");
596         return SETUP (NONE);
597       }
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);
607       }
608       break;
609     default:
610       g_assert_not_reached ();
611       return SETUP (NONE);
612   }
613   if (new_setup == SETUP (NONE)) {
614     GST_ERROR ("Abnormal situation!");
615     return SETUP (NONE);
616   }
617
618   return new_setup;
619 }
620
621 #undef SETUP
622
623 gchar *
624 _generate_fingerprint_from_certificate (gchar * certificate,
625     GChecksumType checksum_type)
626 {
627   gchar **lines, *line;
628   guchar *tmp, *decoded, *digest;
629   GChecksum *checksum;
630   GString *fingerprint;
631   gsize decoded_length, digest_size;
632   gint state = 0;
633   guint save = 0;
634   int i;
635
636   g_return_val_if_fail (certificate != NULL, NULL);
637
638   /* 1. decode the certificate removing newlines and the certificate header
639    * and footer */
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);
645   }
646   g_strfreev (lines);
647   decoded_length = tmp - decoded;
648
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);
655   g_free (decoded);
656
657   /* 3. hex encode the checksum separated with ':'s */
658   fingerprint = g_string_new (NULL);
659   for (i = 0; i < digest_size; i++) {
660     if (i)
661       g_string_append (fingerprint, ":");
662     g_string_append_printf (fingerprint, "%02X", digest[i]);
663   }
664
665   g_free (digest);
666   g_checksum_free (checksum);
667
668   return g_string_free (fingerprint, FALSE);
669 }
670
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" "+/";
675
676 void
677 _generate_ice_credentials (gchar ** ufrag, gchar ** password)
678 {
679   int i;
680
681   *ufrag = g_malloc0 (DEFAULT_ICE_UFRAG_LEN + 1);
682   for (i = 0; i < DEFAULT_ICE_UFRAG_LEN; i++)
683     (*ufrag)[i] =
684         ice_credential_chars[g_random_int_range (0,
685             strlen (ice_credential_chars))];
686
687   *password = g_malloc0 (DEFAULT_ICE_PASSWORD_LEN + 1);
688   for (i = 0; i < DEFAULT_ICE_PASSWORD_LEN; i++)
689     (*password)[i] =
690         ice_credential_chars[g_random_int_range (0,
691             strlen (ice_credential_chars))];
692 }
693
694 int
695 _get_sctp_port_from_media (const GstSDPMedia * media)
696 {
697   int i;
698   const gchar *format;
699   gchar *endptr;
700
701   if (gst_sdp_media_formats_len (media) != 1) {
702     /* only exactly one format is supported */
703     return -1;
704   }
705
706   format = gst_sdp_media_get_format (media, 0);
707
708   if (g_strcmp0 (format, "webrtc-datachannel") == 0) {
709     /* draft-ietf-mmusic-sctp-sdp-21, e.g. Firefox 63 and later */
710
711     for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
712       const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
713
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 */
718           return -1;
719         }
720         return port;
721       }
722     }
723   } else {
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 */
728       return -1;
729     }
730
731     for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
732       const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
733
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) {
738           port = -1;
739         }
740         g_strfreev (parts);
741         return port;
742       }
743     }
744   }
745
746   return -1;
747 }
748
749 guint64
750 _get_sctp_max_message_size_from_media (const GstSDPMedia * media)
751 {
752   int i;
753
754   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
755     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
756
757     if (g_strcmp0 (attr->key, "max-message-size") == 0)
758       return atoi (attr->value);
759   }
760
761   return 65536;
762 }
763
764 gboolean
765 _message_media_is_datachannel (const GstSDPMessage * msg, guint media_id)
766 {
767   const GstSDPMedia *media;
768
769   if (!msg)
770     return FALSE;
771
772   if (gst_sdp_message_medias_len (msg) <= media_id)
773     return FALSE;
774
775   media = gst_sdp_message_get_media (msg, media_id);
776
777   if (g_strcmp0 (gst_sdp_media_get_media (media), "application") != 0)
778     return FALSE;
779
780   if (gst_sdp_media_formats_len (media) != 1)
781     return FALSE;
782
783   if (g_strcmp0 (gst_sdp_media_get_format (media, 0),
784           "webrtc-datachannel") != 0)
785     return FALSE;
786
787   return TRUE;
788 }
789
790 guint
791 _message_get_datachannel_index (const GstSDPMessage * msg)
792 {
793   guint i;
794
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);
798       return i;
799     }
800   }
801
802   return G_MAXUINT;
803 }
804
805 void
806 _get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
807     gchar ** ufrag, gchar ** pwd)
808 {
809   int i;
810
811   *ufrag = NULL;
812   *pwd = NULL;
813
814   {
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);
823       return;
824     }
825   }
826
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);
830
831     if (g_strcmp0 (attr->key, "ice-ufrag") == 0) {
832       g_assert (!*ufrag);
833       *ufrag = g_strdup (attr->value);
834     } else if (g_strcmp0 (attr->key, "ice-pwd") == 0) {
835       g_assert (!*pwd);
836       *pwd = g_strdup (attr->value);
837     }
838   }
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);
850         break;
851       }
852     }
853   }
854 }
855
856 gboolean
857 _parse_bundle (GstSDPMessage * sdp, GStrv * bundled, GError ** error)
858 {
859   const gchar *group;
860   gboolean ret = FALSE;
861
862   group = gst_sdp_message_get_attribute_val (sdp, "group");
863
864   if (group && g_str_has_prefix (group, "BUNDLE ")) {
865     *bundled = g_strsplit (group + strlen ("BUNDLE "), " ", 0);
866
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)",
870           group);
871       g_strfreev (*bundled);
872       *bundled = NULL;
873       goto done;
874     }
875   } else {
876     ret = TRUE;
877     goto done;
878   }
879
880   ret = TRUE;
881
882 done:
883   return ret;
884 }
885
886 gboolean
887 _get_bundle_index (GstSDPMessage * sdp, GStrv bundled, guint * idx)
888 {
889   gboolean ret = FALSE;
890   guint i;
891
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");
895
896     if (!g_strcmp0 (mid, bundled[0])) {
897       *idx = i;
898       ret = TRUE;
899       break;
900     }
901   }
902
903   return ret;
904 }
905
906 gboolean
907 _media_is_bundle_only (const GstSDPMedia * media)
908 {
909   int i;
910
911   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
912     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
913
914     if (g_strcmp0 (attr->key, "bundle-only") == 0) {
915       return TRUE;
916     }
917   }
918
919   return FALSE;
920 }