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