• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 *
_sdp_source_to_string(SDPSource source)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
_check_valid_state_for_sdp_change(GstWebRTCSignalingState state,SDPSource source,GstWebRTCSDPType type,GError ** error)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_ERROR,
88         GST_WEBRTC_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
_check_sdp_crypto(SDPSource source,GstWebRTCSessionDescription * sdp,GError ** error)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_ERROR,
112         GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR, "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_ERROR,
126           GST_WEBRTC_ERROR_FINGERPRINT_FAILURE,
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_ERROR,
136           GST_WEBRTC_ERROR_FINGERPRINT_FAILURE,
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 gboolean
_message_has_attribute_key(const GstSDPMessage * msg,const gchar * key)148 _message_has_attribute_key (const GstSDPMessage * msg, const gchar * key)
149 {
150   int i;
151   for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
152     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
153 
154     if (g_strcmp0 (attr->key, key) == 0)
155       return TRUE;
156   }
157 
158   return FALSE;
159 }
160 
161 #if 0
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_ERROR,
182         GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
183         "No required \'a=ice-options:trickle\' line in sdp");
184   }
185   return TRUE;
186 }
187 #endif
188 gboolean
_media_has_attribute_key(const GstSDPMedia * media,const gchar * key)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
_media_has_mid(const GstSDPMedia * media,guint media_idx,GError ** error)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_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
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 *
_media_get_ice_ufrag(const GstSDPMessage * msg,guint media_idx)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 *
_media_get_ice_pwd(const GstSDPMessage * msg,guint media_idx)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
_media_has_setup(const GstSDPMedia * media,guint media_idx,GError ** error)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_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
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_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
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_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
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
validate_sdp(GstWebRTCSignalingState state,SDPSource source,GstWebRTCSessionDescription * sdp,GError ** error)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_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
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_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
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_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
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_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
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
_get_direction_from_media(const GstSDPMedia * media)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
_intersect_answer_directions(GstWebRTCRTPTransceiverDirection offer,GstWebRTCRTPTransceiverDirection answer)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
_media_replace_direction(GstSDPMedia * media,GstWebRTCRTPTransceiverDirection direction)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
_get_final_direction(GstWebRTCRTPTransceiverDirection local_dir,GstWebRTCRTPTransceiverDirection remote_dir)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
_get_dtls_setup_from_media(const GstSDPMedia * media)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
_intersect_dtls_setup(GstWebRTCDTLSSetup offer)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
_media_replace_setup(GstSDPMedia * media,GstWebRTCDTLSSetup setup)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       g_free (setup_str);
573       return;
574     }
575   }
576 
577   GST_TRACE ("add setup:%s", setup_str);
578   gst_sdp_media_add_attribute (media, "setup", setup_str);
579   g_free (setup_str);
580 }
581 
582 GstWebRTCDTLSSetup
_get_final_setup(GstWebRTCDTLSSetup local_setup,GstWebRTCDTLSSetup remote_setup)583 _get_final_setup (GstWebRTCDTLSSetup local_setup,
584     GstWebRTCDTLSSetup remote_setup)
585 {
586   GstWebRTCDTLSSetup new_setup;
587 
588   new_setup = SETUP (NONE);
589   switch (local_setup) {
590     case SETUP (NONE):
591       /* someone's done a bad job of mangling the SDP. or bugs */
592       g_critical ("Received a locally generated sdp without a parseable "
593           "\'a=setup\' line.  This indicates a bug somewhere.  Bailing");
594       return SETUP (NONE);
595     case SETUP (ACTIVE):
596       if (remote_setup == SETUP (ACTIVE)) {
597         GST_ERROR ("remote SDP has the same "
598             "\'a=setup:active\' attribute. This is not legal");
599         return SETUP (NONE);
600       }
601       new_setup = SETUP (ACTIVE);
602       break;
603     case SETUP (PASSIVE):
604       if (remote_setup == SETUP (PASSIVE)) {
605         GST_ERROR ("remote SDP has the same "
606             "\'a=setup:passive\' attribute. This is not legal");
607         return SETUP (NONE);
608       }
609       new_setup = SETUP (PASSIVE);
610       break;
611     case SETUP (ACTPASS):
612       if (remote_setup == SETUP (ACTPASS)) {
613         GST_ERROR ("remote SDP has the same "
614             "\'a=setup:actpass\' attribute. This is not legal");
615         return SETUP (NONE);
616       }
617       if (remote_setup == SETUP (ACTIVE))
618         new_setup = SETUP (PASSIVE);
619       else if (remote_setup == SETUP (PASSIVE))
620         new_setup = SETUP (ACTIVE);
621       else if (remote_setup == SETUP (NONE)) {
622         /* XXX: what to do here? */
623         GST_WARNING ("unspecified situation. local: "
624             "\'a=setup:actpass\' remote: none/unparseable");
625         new_setup = SETUP (ACTIVE);
626       }
627       break;
628     default:
629       g_assert_not_reached ();
630       return SETUP (NONE);
631   }
632   if (new_setup == SETUP (NONE)) {
633     GST_ERROR ("Abnormal situation!");
634     return SETUP (NONE);
635   }
636 
637   return new_setup;
638 }
639 
640 #undef SETUP
641 
642 gchar *
_generate_fingerprint_from_certificate(gchar * certificate,GChecksumType checksum_type)643 _generate_fingerprint_from_certificate (gchar * certificate,
644     GChecksumType checksum_type)
645 {
646   gchar **lines, *line;
647   guchar *tmp, *decoded, *digest;
648   GChecksum *checksum;
649   GString *fingerprint;
650   gsize decoded_length, digest_size;
651   gint state = 0;
652   guint save = 0;
653   int i;
654 
655   g_return_val_if_fail (certificate != NULL, NULL);
656 
657   /* 1. decode the certificate removing newlines and the certificate header
658    * and footer */
659   decoded = tmp = g_new0 (guchar, (strlen (certificate) / 4) * 3 + 3);
660   lines = g_strsplit (certificate, "\n", 0);
661   for (i = 0, line = lines[i]; line; line = lines[++i]) {
662     if (line[0] && !g_str_has_prefix (line, "-----"))
663       tmp += g_base64_decode_step (line, strlen (line), tmp, &state, &save);
664   }
665   g_strfreev (lines);
666   decoded_length = tmp - decoded;
667 
668   /* 2. compute a checksum of the decoded certificate */
669   checksum = g_checksum_new (checksum_type);
670   digest_size = g_checksum_type_get_length (checksum_type);
671   digest = g_new (guint8, digest_size);
672   g_checksum_update (checksum, decoded, decoded_length);
673   g_checksum_get_digest (checksum, digest, &digest_size);
674   g_free (decoded);
675 
676   /* 3. hex encode the checksum separated with ':'s */
677   fingerprint = g_string_new (NULL);
678   for (i = 0; i < digest_size; i++) {
679     if (i)
680       g_string_append (fingerprint, ":");
681     g_string_append_printf (fingerprint, "%02X", digest[i]);
682   }
683 
684   g_free (digest);
685   g_checksum_free (checksum);
686 
687   return g_string_free (fingerprint, FALSE);
688 }
689 
690 #define DEFAULT_ICE_UFRAG_LEN 32
691 #define DEFAULT_ICE_PASSWORD_LEN 32
692 static const gchar *ice_credential_chars =
693     "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/";
694 
695 void
_generate_ice_credentials(gchar ** ufrag,gchar ** password)696 _generate_ice_credentials (gchar ** ufrag, gchar ** password)
697 {
698   int i;
699 
700   *ufrag = g_malloc0 (DEFAULT_ICE_UFRAG_LEN + 1);
701   for (i = 0; i < DEFAULT_ICE_UFRAG_LEN; i++)
702     (*ufrag)[i] =
703         ice_credential_chars[g_random_int_range (0,
704             strlen (ice_credential_chars))];
705 
706   *password = g_malloc0 (DEFAULT_ICE_PASSWORD_LEN + 1);
707   for (i = 0; i < DEFAULT_ICE_PASSWORD_LEN; i++)
708     (*password)[i] =
709         ice_credential_chars[g_random_int_range (0,
710             strlen (ice_credential_chars))];
711 }
712 
713 int
_get_sctp_port_from_media(const GstSDPMedia * media)714 _get_sctp_port_from_media (const GstSDPMedia * media)
715 {
716   int i;
717   const gchar *format;
718   gchar *endptr;
719 
720   if (gst_sdp_media_formats_len (media) != 1) {
721     /* only exactly one format is supported */
722     return -1;
723   }
724 
725   format = gst_sdp_media_get_format (media, 0);
726 
727   if (g_strcmp0 (format, "webrtc-datachannel") == 0) {
728     /* draft-ietf-mmusic-sctp-sdp-21, e.g. Firefox 63 and later */
729 
730     for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
731       const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
732 
733       if (g_strcmp0 (attr->key, "sctp-port") == 0) {
734         gint64 port = g_ascii_strtoll (attr->value, &endptr, 10);
735         if (endptr == attr->value) {
736           /* conversion error */
737           return -1;
738         }
739         return port;
740       }
741     }
742   } else {
743     /* draft-ietf-mmusic-sctp-sdp-05, e.g. Chrome as recent as 75 */
744     gint64 port = g_ascii_strtoll (format, &endptr, 10);
745     if (endptr == format) {
746       /* conversion error */
747       return -1;
748     }
749 
750     for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
751       const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
752 
753       if (g_strcmp0 (attr->key, "sctpmap") == 0 && atoi (attr->value) == port) {
754         /* a=sctpmap:5000 webrtc-datachannel 256 */
755         gchar **parts = g_strsplit (attr->value, " ", 3);
756         if (!parts[1] || g_strcmp0 (parts[1], "webrtc-datachannel") != 0) {
757           port = -1;
758         }
759         g_strfreev (parts);
760         return port;
761       }
762     }
763   }
764 
765   return -1;
766 }
767 
768 guint64
_get_sctp_max_message_size_from_media(const GstSDPMedia * media)769 _get_sctp_max_message_size_from_media (const GstSDPMedia * media)
770 {
771   int i;
772 
773   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
774     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
775 
776     if (g_strcmp0 (attr->key, "max-message-size") == 0)
777       return atoi (attr->value);
778   }
779 
780   return 65536;
781 }
782 
783 gboolean
_message_media_is_datachannel(const GstSDPMessage * msg,guint media_id)784 _message_media_is_datachannel (const GstSDPMessage * msg, guint media_id)
785 {
786   const GstSDPMedia *media;
787 
788   if (!msg)
789     return FALSE;
790 
791   if (gst_sdp_message_medias_len (msg) <= media_id)
792     return FALSE;
793 
794   media = gst_sdp_message_get_media (msg, media_id);
795 
796   if (g_strcmp0 (gst_sdp_media_get_media (media), "application") != 0)
797     return FALSE;
798 
799   if (gst_sdp_media_formats_len (media) != 1)
800     return FALSE;
801 
802   if (g_strcmp0 (gst_sdp_media_get_format (media, 0),
803           "webrtc-datachannel") != 0)
804     return FALSE;
805 
806   return TRUE;
807 }
808 
809 guint
_message_get_datachannel_index(const GstSDPMessage * msg)810 _message_get_datachannel_index (const GstSDPMessage * msg)
811 {
812   guint i;
813 
814   for (i = 0; i < gst_sdp_message_medias_len (msg); i++) {
815     if (_message_media_is_datachannel (msg, i)) {
816       g_assert (i < G_MAXUINT);
817       return i;
818     }
819   }
820 
821   return G_MAXUINT;
822 }
823 
824 void
_get_ice_credentials_from_sdp_media(const GstSDPMessage * sdp,guint media_idx,gchar ** ufrag,gchar ** pwd)825 _get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
826     gchar ** ufrag, gchar ** pwd)
827 {
828   int i;
829 
830   *ufrag = NULL;
831   *pwd = NULL;
832 
833   {
834     /* search in the corresponding media section */
835     const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx);
836     const gchar *tmp_ufrag =
837         gst_sdp_media_get_attribute_val (media, "ice-ufrag");
838     const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
839     if (tmp_ufrag && tmp_pwd) {
840       *ufrag = g_strdup (tmp_ufrag);
841       *pwd = g_strdup (tmp_pwd);
842       return;
843     }
844   }
845 
846   /* then in the sdp message itself */
847   for (i = 0; i < gst_sdp_message_attributes_len (sdp); i++) {
848     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (sdp, i);
849 
850     if (g_strcmp0 (attr->key, "ice-ufrag") == 0) {
851       g_assert (!*ufrag);
852       *ufrag = g_strdup (attr->value);
853     } else if (g_strcmp0 (attr->key, "ice-pwd") == 0) {
854       g_assert (!*pwd);
855       *pwd = g_strdup (attr->value);
856     }
857   }
858   if (!*ufrag && !*pwd) {
859     /* Check in the medias themselves. According to JSEP, they should be
860      * identical FIXME: only for bundle-d streams */
861     for (i = 0; i < gst_sdp_message_medias_len (sdp); i++) {
862       const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
863       const gchar *tmp_ufrag =
864           gst_sdp_media_get_attribute_val (media, "ice-ufrag");
865       const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
866       if (tmp_ufrag && tmp_pwd) {
867         *ufrag = g_strdup (tmp_ufrag);
868         *pwd = g_strdup (tmp_pwd);
869         break;
870       }
871     }
872   }
873 }
874 
875 gboolean
_parse_bundle(GstSDPMessage * sdp,GStrv * bundled,GError ** error)876 _parse_bundle (GstSDPMessage * sdp, GStrv * bundled, GError ** error)
877 {
878   const gchar *group;
879   gboolean ret = FALSE;
880 
881   group = gst_sdp_message_get_attribute_val (sdp, "group");
882 
883   if (group && g_str_has_prefix (group, "BUNDLE ")) {
884     *bundled = g_strsplit (group + strlen ("BUNDLE "), " ", 0);
885 
886     if (!(*bundled)[0]) {
887       g_set_error (error, GST_WEBRTC_ERROR, GST_WEBRTC_ERROR_SDP_SYNTAX_ERROR,
888           "Invalid format for BUNDLE group, expected at least one mid (%s)",
889           group);
890       g_strfreev (*bundled);
891       *bundled = NULL;
892       goto done;
893     }
894   } else {
895     ret = TRUE;
896     goto done;
897   }
898 
899   ret = TRUE;
900 
901 done:
902   return ret;
903 }
904 
905 gboolean
_get_bundle_index(GstSDPMessage * sdp,GStrv bundled,guint * idx)906 _get_bundle_index (GstSDPMessage * sdp, GStrv bundled, guint * idx)
907 {
908   gboolean ret = FALSE;
909   guint i;
910 
911   for (i = 0; i < gst_sdp_message_medias_len (sdp); i++) {
912     const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
913     const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
914 
915     if (!g_strcmp0 (mid, bundled[0])) {
916       *idx = i;
917       ret = TRUE;
918       break;
919     }
920   }
921 
922   return ret;
923 }
924 
925 gboolean
_media_is_bundle_only(const GstSDPMedia * media)926 _media_is_bundle_only (const GstSDPMedia * media)
927 {
928   int i;
929 
930   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
931     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
932 
933     if (g_strcmp0 (attr->key, "bundle-only") == 0) {
934       return TRUE;
935     }
936   }
937 
938   return FALSE;
939 }
940