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