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_BIN_ERROR,
88         GST_WEBRTC_BIN_ERROR_INVALID_STATE,
89         "Not in the correct state (%s) for setting %s %s description",
90         state_str, _sdp_source_to_string (source), type_str);
91     g_free (state_str);
92     g_free (type_str);
93   }
94 
95   return FALSE;
96 
97 #undef STATE
98 #undef TYPE
99 }
100 
101 static gboolean
_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_BIN_ERROR,
112         GST_WEBRTC_BIN_ERROR_BAD_SDP, "sdp contains a k line");
113     return FALSE;
114   }
115 
116   message_fingerprint = fingerprint =
117       gst_sdp_message_get_attribute_val (sdp->sdp, "fingerprint");
118   for (i = 0; i < gst_sdp_message_medias_len (sdp->sdp); i++) {
119     const GstSDPMedia *media = gst_sdp_message_get_media (sdp->sdp, i);
120     const gchar *media_fingerprint =
121         gst_sdp_media_get_attribute_val (media, "fingerprint");
122 
123     if (!IS_EMPTY_SDP_ATTRIBUTE (message_fingerprint)
124         && !IS_EMPTY_SDP_ATTRIBUTE (media_fingerprint)) {
125       g_set_error (error, GST_WEBRTC_BIN_ERROR,
126           GST_WEBRTC_BIN_ERROR_FINGERPRINT,
127           "No fingerprint lines in sdp for media %u", i);
128       return FALSE;
129     }
130     if (IS_EMPTY_SDP_ATTRIBUTE (fingerprint)) {
131       fingerprint = media_fingerprint;
132     }
133     if (!IS_EMPTY_SDP_ATTRIBUTE (media_fingerprint)
134         && g_strcmp0 (fingerprint, media_fingerprint) != 0) {
135       g_set_error (error, GST_WEBRTC_BIN_ERROR,
136           GST_WEBRTC_BIN_ERROR_FINGERPRINT,
137           "Fingerprint in media %u differs from %s fingerprint. "
138           "\'%s\' != \'%s\'", i, message_fingerprint ? "global" : "previous",
139           fingerprint, media_fingerprint);
140       return FALSE;
141     }
142   }
143 
144   return TRUE;
145 }
146 
147 #if 0
148 static gboolean
149 _session_has_attribute_key (const GstSDPMessage * msg, const gchar * key)
150 {
151   int i;
152   for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
153     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
154 
155     if (g_strcmp0 (attr->key, key) == 0)
156       return TRUE;
157   }
158 
159   return FALSE;
160 }
161 
162 static gboolean
163 _session_has_attribute_key_value (const GstSDPMessage * msg, const gchar * key,
164     const gchar * value)
165 {
166   int i;
167   for (i = 0; i < gst_sdp_message_attributes_len (msg); i++) {
168     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (msg, i);
169 
170     if (g_strcmp0 (attr->key, key) == 0 && g_strcmp0 (attr->value, value) == 0)
171       return TRUE;
172   }
173 
174   return FALSE;
175 }
176 
177 static gboolean
178 _check_trickle_ice (GstSDPMessage * msg, GError ** error)
179 {
180   if (!_session_has_attribute_key_value (msg, "ice-options", "trickle")) {
181     g_set_error_literal (error, GST_WEBRTC_BIN_ERROR,
182         GST_WEBRTC_BIN_ERROR_BAD_SDP,
183         "No required \'a=ice-options:trickle\' line in sdp");
184   }
185   return TRUE;
186 }
187 #endif
188 gboolean
_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_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
208         "media %u is missing or contains an empty \'mid\' attribute",
209         media_idx);
210     return FALSE;
211   }
212   return TRUE;
213 }
214 
215 static 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 static 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_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
252         "media %u is missing or contains an empty \'setup\' attribute",
253         media_idx);
254     return FALSE;
255   }
256   if (!g_strv_contains (valid_setups, setup)) {
257     g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
258         "media %u contains unknown \'setup\' attribute, \'%s\'", media_idx,
259         setup);
260     return FALSE;
261   }
262   return TRUE;
263 }
264 
265 #if 0
266 static gboolean
267 _media_has_dtls_id (const GstSDPMedia * media, guint media_idx, GError ** error)
268 {
269   const gchar *dtls_id = gst_sdp_media_get_attribute_val (media, "ice-pwd");
270   if (IS_EMPTY_SDP_ATTRIBUTE (dtls_id)) {
271     g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
272         "media %u is missing or contains an empty \'dtls-id\' attribute",
273         media_idx);
274     return FALSE;
275   }
276   return TRUE;
277 }
278 #endif
279 gboolean
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_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
311           "media %u is missing or contains an empty \'ice-ufrag\' attribute",
312           i);
313       goto fail;
314     }
315     if (!_media_get_ice_pwd (sdp->sdp, i)) {
316       g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
317           "media %u is missing or contains an empty \'ice-pwd\' attribute", i);
318       goto fail;
319     }
320     if (!_media_has_setup (media, i, error))
321       goto fail;
322     /* check paramaters in bundle are the same */
323     if (media_in_bundle) {
324       const gchar *ice_ufrag =
325           gst_sdp_media_get_attribute_val (media, "ice-ufrag");
326       const gchar *ice_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
327       if (!bundle_ice_ufrag)
328         bundle_ice_ufrag = ice_ufrag;
329       else if (g_strcmp0 (bundle_ice_ufrag, ice_ufrag) != 0) {
330         g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
331             "media %u has different ice-ufrag values in bundle. "
332             "%s != %s", i, bundle_ice_ufrag, ice_ufrag);
333         goto fail;
334       }
335       if (!bundle_ice_pwd) {
336         bundle_ice_pwd = ice_pwd;
337       } else if (g_strcmp0 (bundle_ice_pwd, ice_pwd) != 0) {
338         g_set_error (error, GST_WEBRTC_BIN_ERROR, GST_WEBRTC_BIN_ERROR_BAD_SDP,
339             "media %u has different ice-pwd values in bundle. "
340             "%s != %s", i, bundle_ice_pwd, ice_pwd);
341         goto fail;
342       }
343     }
344   }
345 
346   g_strfreev (group_members);
347 
348   return TRUE;
349 
350 fail:
351   g_strfreev (group_members);
352   return FALSE;
353 }
354 
355 GstWebRTCRTPTransceiverDirection
_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 (SENDONLY) && answer == DIR (SENDRECV))
401     return DIR (RECVONLY);
402   if (offer == DIR (SENDONLY) && answer == DIR (RECVONLY))
403     return DIR (RECVONLY);
404   if (offer == DIR (RECVONLY) && answer == DIR (SENDRECV))
405     return DIR (SENDONLY);
406   if (offer == DIR (RECVONLY) && answer == DIR (SENDONLY))
407     return DIR (SENDONLY);
408   if (offer == DIR (SENDRECV) && answer == DIR (SENDRECV))
409     return DIR (SENDRECV);
410   if (offer == DIR (SENDRECV) && answer == DIR (SENDONLY))
411     return DIR (SENDONLY);
412   if (offer == DIR (SENDRECV) && answer == DIR (RECVONLY))
413     return DIR (RECVONLY);
414 
415   return DIR (NONE);
416 }
417 
418 void
_media_replace_direction(GstSDPMedia * media,GstWebRTCRTPTransceiverDirection direction)419 _media_replace_direction (GstSDPMedia * media,
420     GstWebRTCRTPTransceiverDirection direction)
421 {
422   gchar *dir_str;
423   int i;
424 
425   dir_str =
426       _enum_value_to_string (GST_TYPE_WEBRTC_RTP_TRANSCEIVER_DIRECTION,
427       direction);
428 
429   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
430     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
431 
432     if (g_strcmp0 (attr->key, "sendonly") == 0
433         || g_strcmp0 (attr->key, "sendrecv") == 0
434         || g_strcmp0 (attr->key, "recvonly") == 0) {
435       GstSDPAttribute new_attr = { 0, };
436       GST_TRACE ("replace %s with %s", attr->key, dir_str);
437       gst_sdp_attribute_set (&new_attr, dir_str, "");
438       gst_sdp_media_replace_attribute (media, i, &new_attr);
439       return;
440     }
441   }
442 
443   GST_TRACE ("add %s", dir_str);
444   gst_sdp_media_add_attribute (media, dir_str, "");
445   g_free (dir_str);
446 }
447 
448 GstWebRTCRTPTransceiverDirection
_get_final_direction(GstWebRTCRTPTransceiverDirection local_dir,GstWebRTCRTPTransceiverDirection remote_dir)449 _get_final_direction (GstWebRTCRTPTransceiverDirection local_dir,
450     GstWebRTCRTPTransceiverDirection remote_dir)
451 {
452   GstWebRTCRTPTransceiverDirection new_dir;
453   new_dir = DIR (NONE);
454   switch (local_dir) {
455     case DIR (INACTIVE):
456       new_dir = DIR (INACTIVE);
457       break;
458     case DIR (SENDONLY):
459       if (remote_dir == DIR (SENDONLY)) {
460         GST_ERROR ("remote SDP has the same directionality. "
461             "This is not legal.");
462         return DIR (NONE);
463       } else if (remote_dir == DIR (INACTIVE)) {
464         new_dir = DIR (INACTIVE);
465       } else {
466         new_dir = DIR (SENDONLY);
467       }
468       break;
469     case DIR (RECVONLY):
470       if (remote_dir == DIR (RECVONLY)) {
471         GST_ERROR ("remote SDP has the same directionality. "
472             "This is not legal.");
473         return DIR (NONE);
474       } else if (remote_dir == DIR (INACTIVE)) {
475         new_dir = DIR (INACTIVE);
476       } else {
477         new_dir = DIR (RECVONLY);
478       }
479       break;
480     case DIR (SENDRECV):
481       if (remote_dir == DIR (INACTIVE)) {
482         new_dir = DIR (INACTIVE);
483       } else if (remote_dir == DIR (SENDONLY)) {
484         new_dir = DIR (RECVONLY);
485       } else if (remote_dir == DIR (RECVONLY)) {
486         new_dir = DIR (SENDONLY);
487       } else if (remote_dir == DIR (SENDRECV)) {
488         new_dir = DIR (SENDRECV);
489       }
490       break;
491     default:
492       g_assert_not_reached ();
493       break;
494   }
495 
496   if (new_dir == DIR (NONE)) {
497     GST_ERROR ("Abnormal situation!");
498     return DIR (NONE);
499   }
500 
501   return new_dir;
502 }
503 
504 #undef DIR
505 
506 #define SETUP(val) GST_WEBRTC_DTLS_SETUP_ ## val
507 GstWebRTCDTLSSetup
_get_dtls_setup_from_media(const GstSDPMedia * media)508 _get_dtls_setup_from_media (const GstSDPMedia * media)
509 {
510   int i;
511 
512   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
513     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
514 
515     if (g_strcmp0 (attr->key, "setup") == 0) {
516       if (g_strcmp0 (attr->value, "actpass") == 0) {
517         return SETUP (ACTPASS);
518       } else if (g_strcmp0 (attr->value, "active") == 0) {
519         return SETUP (ACTIVE);
520       } else if (g_strcmp0 (attr->value, "passive") == 0) {
521         return SETUP (PASSIVE);
522       } else {
523         GST_ERROR ("unknown setup value %s", attr->value);
524         return SETUP (NONE);
525       }
526     }
527   }
528 
529   GST_LOG ("no setup attribute in media");
530   return SETUP (NONE);
531 }
532 
533 GstWebRTCDTLSSetup
_intersect_dtls_setup(GstWebRTCDTLSSetup offer)534 _intersect_dtls_setup (GstWebRTCDTLSSetup offer)
535 {
536   switch (offer) {
537     case SETUP (NONE):         /* default is active */
538     case SETUP (ACTPASS):
539     case SETUP (PASSIVE):
540       return SETUP (ACTIVE);
541     case SETUP (ACTIVE):
542       return SETUP (PASSIVE);
543     default:
544       return SETUP (NONE);
545   }
546 }
547 
548 void
_media_replace_setup(GstSDPMedia * media,GstWebRTCDTLSSetup setup)549 _media_replace_setup (GstSDPMedia * media, GstWebRTCDTLSSetup setup)
550 {
551   gchar *setup_str;
552   int i;
553 
554   setup_str = _enum_value_to_string (GST_TYPE_WEBRTC_DTLS_SETUP, setup);
555 
556   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
557     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
558 
559     if (g_strcmp0 (attr->key, "setup") == 0) {
560       GstSDPAttribute new_attr = { 0, };
561       GST_TRACE ("replace setup:%s with setup:%s", attr->value, setup_str);
562       gst_sdp_attribute_set (&new_attr, "setup", setup_str);
563       gst_sdp_media_replace_attribute (media, i, &new_attr);
564       return;
565     }
566   }
567 
568   GST_TRACE ("add setup:%s", setup_str);
569   gst_sdp_media_add_attribute (media, "setup", setup_str);
570   g_free (setup_str);
571 }
572 
573 GstWebRTCDTLSSetup
_get_final_setup(GstWebRTCDTLSSetup local_setup,GstWebRTCDTLSSetup remote_setup)574 _get_final_setup (GstWebRTCDTLSSetup local_setup,
575     GstWebRTCDTLSSetup remote_setup)
576 {
577   GstWebRTCDTLSSetup new_setup;
578 
579   new_setup = SETUP (NONE);
580   switch (local_setup) {
581     case SETUP (NONE):
582       /* someone's done a bad job of mangling the SDP. or bugs */
583       g_critical ("Received a locally generated sdp without a parseable "
584           "\'a=setup\' line.  This indicates a bug somewhere.  Bailing");
585       return SETUP (NONE);
586     case SETUP (ACTIVE):
587       if (remote_setup == SETUP (ACTIVE)) {
588         GST_ERROR ("remote SDP has the same "
589             "\'a=setup:active\' attribute. This is not legal");
590         return SETUP (NONE);
591       }
592       new_setup = SETUP (ACTIVE);
593       break;
594     case SETUP (PASSIVE):
595       if (remote_setup == SETUP (PASSIVE)) {
596         GST_ERROR ("remote SDP has the same "
597             "\'a=setup:passive\' attribute. This is not legal");
598         return SETUP (NONE);
599       }
600       new_setup = SETUP (PASSIVE);
601       break;
602     case SETUP (ACTPASS):
603       if (remote_setup == SETUP (ACTPASS)) {
604         GST_ERROR ("remote SDP has the same "
605             "\'a=setup:actpass\' attribute. This is not legal");
606         return SETUP (NONE);
607       }
608       if (remote_setup == SETUP (ACTIVE))
609         new_setup = SETUP (PASSIVE);
610       else if (remote_setup == SETUP (PASSIVE))
611         new_setup = SETUP (ACTIVE);
612       else if (remote_setup == SETUP (NONE)) {
613         /* XXX: what to do here? */
614         GST_WARNING ("unspecified situation. local: "
615             "\'a=setup:actpass\' remote: none/unparseable");
616         new_setup = SETUP (ACTIVE);
617       }
618       break;
619     default:
620       g_assert_not_reached ();
621       return SETUP (NONE);
622   }
623   if (new_setup == SETUP (NONE)) {
624     GST_ERROR ("Abnormal situation!");
625     return SETUP (NONE);
626   }
627 
628   return new_setup;
629 }
630 
631 #undef SETUP
632 
633 gchar *
_generate_fingerprint_from_certificate(gchar * certificate,GChecksumType checksum_type)634 _generate_fingerprint_from_certificate (gchar * certificate,
635     GChecksumType checksum_type)
636 {
637   gchar **lines, *line;
638   guchar *tmp, *decoded, *digest;
639   GChecksum *checksum;
640   GString *fingerprint;
641   gsize decoded_length, digest_size;
642   gint state = 0;
643   guint save = 0;
644   int i;
645 
646   g_return_val_if_fail (certificate != NULL, NULL);
647 
648   /* 1. decode the certificate removing newlines and the certificate header
649    * and footer */
650   decoded = tmp = g_new0 (guchar, (strlen (certificate) / 4) * 3 + 3);
651   lines = g_strsplit (certificate, "\n", 0);
652   for (i = 0, line = lines[i]; line; line = lines[++i]) {
653     if (line[0] && !g_str_has_prefix (line, "-----"))
654       tmp += g_base64_decode_step (line, strlen (line), tmp, &state, &save);
655   }
656   g_strfreev (lines);
657   decoded_length = tmp - decoded;
658 
659   /* 2. compute a checksum of the decoded certificate */
660   checksum = g_checksum_new (checksum_type);
661   digest_size = g_checksum_type_get_length (checksum_type);
662   digest = g_new (guint8, digest_size);
663   g_checksum_update (checksum, decoded, decoded_length);
664   g_checksum_get_digest (checksum, digest, &digest_size);
665   g_free (decoded);
666 
667   /* 3. hex encode the checksum separated with ':'s */
668   fingerprint = g_string_new (NULL);
669   for (i = 0; i < digest_size; i++) {
670     if (i)
671       g_string_append (fingerprint, ":");
672     g_string_append_printf (fingerprint, "%02X", digest[i]);
673   }
674 
675   g_free (digest);
676   g_checksum_free (checksum);
677 
678   return g_string_free (fingerprint, FALSE);
679 }
680 
681 #define DEFAULT_ICE_UFRAG_LEN 32
682 #define DEFAULT_ICE_PASSWORD_LEN 32
683 static const gchar *ice_credential_chars =
684     "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "abcdefghijklmnopqrstuvwxyz" "0123456789" "+/";
685 
686 void
_generate_ice_credentials(gchar ** ufrag,gchar ** password)687 _generate_ice_credentials (gchar ** ufrag, gchar ** password)
688 {
689   int i;
690 
691   *ufrag = g_malloc0 (DEFAULT_ICE_UFRAG_LEN + 1);
692   for (i = 0; i < DEFAULT_ICE_UFRAG_LEN; i++)
693     (*ufrag)[i] =
694         ice_credential_chars[g_random_int_range (0,
695             strlen (ice_credential_chars))];
696 
697   *password = g_malloc0 (DEFAULT_ICE_PASSWORD_LEN + 1);
698   for (i = 0; i < DEFAULT_ICE_PASSWORD_LEN; i++)
699     (*password)[i] =
700         ice_credential_chars[g_random_int_range (0,
701             strlen (ice_credential_chars))];
702 }
703 
704 int
_get_sctp_port_from_media(const GstSDPMedia * media)705 _get_sctp_port_from_media (const GstSDPMedia * media)
706 {
707   int sctpmap = -1, i;
708 
709   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
710     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
711 
712     if (g_strcmp0 (attr->key, "sctp-port") == 0) {
713       return atoi (attr->value);
714     } else if (g_strcmp0 (attr->key, "sctpmap") == 0) {
715       sctpmap = atoi (attr->value);
716     }
717   }
718 
719   if (sctpmap >= 0)
720     GST_LOG ("no sctp-port attribute in media");
721   return sctpmap;
722 }
723 
724 guint64
_get_sctp_max_message_size_from_media(const GstSDPMedia * media)725 _get_sctp_max_message_size_from_media (const GstSDPMedia * media)
726 {
727   int i;
728 
729   for (i = 0; i < gst_sdp_media_attributes_len (media); i++) {
730     const GstSDPAttribute *attr = gst_sdp_media_get_attribute (media, i);
731 
732     if (g_strcmp0 (attr->key, "max-message-size") == 0)
733       return atoi (attr->value);
734   }
735 
736   return 65536;
737 }
738 
739 gboolean
_message_media_is_datachannel(const GstSDPMessage * msg,guint media_id)740 _message_media_is_datachannel (const GstSDPMessage * msg, guint media_id)
741 {
742   const GstSDPMedia *media;
743 
744   if (!msg)
745     return FALSE;
746 
747   if (gst_sdp_message_medias_len (msg) <= media_id)
748     return FALSE;
749 
750   media = gst_sdp_message_get_media (msg, media_id);
751 
752   if (g_strcmp0 (gst_sdp_media_get_media (media), "application") != 0)
753     return FALSE;
754 
755   if (gst_sdp_media_formats_len (media) != 1)
756     return FALSE;
757 
758   if (g_strcmp0 (gst_sdp_media_get_format (media, 0),
759           "webrtc-datachannel") != 0)
760     return FALSE;
761 
762   return TRUE;
763 }
764 
765 void
_get_ice_credentials_from_sdp_media(const GstSDPMessage * sdp,guint media_idx,gchar ** ufrag,gchar ** pwd)766 _get_ice_credentials_from_sdp_media (const GstSDPMessage * sdp, guint media_idx,
767     gchar ** ufrag, gchar ** pwd)
768 {
769   int i;
770 
771   *ufrag = NULL;
772   *pwd = NULL;
773 
774   {
775     /* search in the corresponding media section */
776     const GstSDPMedia *media = gst_sdp_message_get_media (sdp, media_idx);
777     const gchar *tmp_ufrag =
778         gst_sdp_media_get_attribute_val (media, "ice-ufrag");
779     const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
780     if (tmp_ufrag && tmp_pwd) {
781       *ufrag = g_strdup (tmp_ufrag);
782       *pwd = g_strdup (tmp_pwd);
783       return;
784     }
785   }
786 
787   /* then in the sdp message itself */
788   for (i = 0; i < gst_sdp_message_attributes_len (sdp); i++) {
789     const GstSDPAttribute *attr = gst_sdp_message_get_attribute (sdp, i);
790 
791     if (g_strcmp0 (attr->key, "ice-ufrag") == 0) {
792       g_assert (!*ufrag);
793       *ufrag = g_strdup (attr->value);
794     } else if (g_strcmp0 (attr->key, "ice-pwd") == 0) {
795       g_assert (!*pwd);
796       *pwd = g_strdup (attr->value);
797     }
798   }
799   if (!*ufrag && !*pwd) {
800     /* Check in the medias themselves. According to JSEP, they should be
801      * identical FIXME: only for bundle-d streams */
802     for (i = 0; i < gst_sdp_message_medias_len (sdp); i++) {
803       const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
804       const gchar *tmp_ufrag =
805           gst_sdp_media_get_attribute_val (media, "ice-ufrag");
806       const gchar *tmp_pwd = gst_sdp_media_get_attribute_val (media, "ice-pwd");
807       if (tmp_ufrag && tmp_pwd) {
808         *ufrag = g_strdup (tmp_ufrag);
809         *pwd = g_strdup (tmp_pwd);
810         break;
811       }
812     }
813   }
814 }
815 
816 gboolean
_parse_bundle(GstSDPMessage * sdp,GStrv * bundled)817 _parse_bundle (GstSDPMessage * sdp, GStrv * bundled)
818 {
819   const gchar *group;
820   gboolean ret = FALSE;
821 
822   group = gst_sdp_message_get_attribute_val (sdp, "group");
823 
824   if (group && g_str_has_prefix (group, "BUNDLE ")) {
825     *bundled = g_strsplit (group + strlen ("BUNDLE "), " ", 0);
826 
827     if (!(*bundled)[0]) {
828       GST_ERROR ("Invalid format for BUNDLE group, expected at least "
829           "one mid (%s)", group);
830       goto done;
831     }
832   } else {
833     ret = TRUE;
834     goto done;
835   }
836 
837   ret = TRUE;
838 
839 done:
840   return ret;
841 }
842 
843 gboolean
_get_bundle_index(GstSDPMessage * sdp,GStrv bundled,guint * idx)844 _get_bundle_index (GstSDPMessage * sdp, GStrv bundled, guint * idx)
845 {
846   gboolean ret = FALSE;
847   guint i;
848 
849   for (i = 0; i < gst_sdp_message_medias_len (sdp); i++) {
850     const GstSDPMedia *media = gst_sdp_message_get_media (sdp, i);
851     const gchar *mid = gst_sdp_media_get_attribute_val (media, "mid");
852 
853     if (!g_strcmp0 (mid, bundled[0])) {
854       *idx = i;
855       ret = TRUE;
856       break;
857     }
858   }
859 
860   return ret;
861 }
862