Merging gst-python
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-bad / ext / webrtc / gstwebrtcice.c
1 /* GStreamer
2  * Copyright (C) 2017 Matthew Waters <matthew@centricular.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #include "gstwebrtcice.h"
25 /* libnice */
26 #include <agent.h>
27 #include "icestream.h"
28 #include "nicetransport.h"
29
30 /* XXX:
31  *
32  * - are locally generated remote candidates meant to be readded to libnice?
33  */
34
35 static GstUri *_validate_turn_server (GstWebRTCICE * ice, const gchar * s);
36
37 #define GST_CAT_DEFAULT gst_webrtc_ice_debug
38 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
39
40 GQuark
41 gst_webrtc_ice_error_quark (void)
42 {
43   return g_quark_from_static_string ("gst-webrtc-ice-error-quark");
44 }
45
46 enum
47 {
48   SIGNAL_0,
49   ADD_LOCAL_IP_ADDRESS_SIGNAL,
50   LAST_SIGNAL,
51 };
52
53 enum
54 {
55   PROP_0,
56   PROP_AGENT,
57   PROP_ICE_TCP,
58   PROP_ICE_UDP,
59   PROP_MIN_RTP_PORT,
60   PROP_MAX_RTP_PORT,
61 };
62
63 static guint gst_webrtc_ice_signals[LAST_SIGNAL] = { 0 };
64
65 struct _GstWebRTCICEPrivate
66 {
67   NiceAgent *nice_agent;
68
69   GArray *nice_stream_map;
70
71   GThread *thread;
72   GMainContext *main_context;
73   GMainLoop *loop;
74   GMutex lock;
75   GCond cond;
76
77   GstWebRTCIceOnCandidateFunc on_candidate;
78   gpointer on_candidate_data;
79   GDestroyNotify on_candidate_notify;
80 };
81
82 #define gst_webrtc_ice_parent_class parent_class
83 G_DEFINE_TYPE_WITH_CODE (GstWebRTCICE, gst_webrtc_ice,
84     GST_TYPE_OBJECT, G_ADD_PRIVATE (GstWebRTCICE)
85     GST_DEBUG_CATEGORY_INIT (gst_webrtc_ice_debug, "webrtcice", 0,
86         "webrtcice"););
87
88 static gboolean
89 _unlock_pc_thread (GMutex * lock)
90 {
91   g_mutex_unlock (lock);
92   return G_SOURCE_REMOVE;
93 }
94
95 static gpointer
96 _gst_nice_thread (GstWebRTCICE * ice)
97 {
98   g_mutex_lock (&ice->priv->lock);
99   ice->priv->main_context = g_main_context_new ();
100   ice->priv->loop = g_main_loop_new (ice->priv->main_context, FALSE);
101
102   g_cond_broadcast (&ice->priv->cond);
103   g_main_context_invoke (ice->priv->main_context,
104       (GSourceFunc) _unlock_pc_thread, &ice->priv->lock);
105
106   g_main_loop_run (ice->priv->loop);
107
108   g_mutex_lock (&ice->priv->lock);
109   g_main_context_unref (ice->priv->main_context);
110   ice->priv->main_context = NULL;
111   g_main_loop_unref (ice->priv->loop);
112   ice->priv->loop = NULL;
113   g_cond_broadcast (&ice->priv->cond);
114   g_mutex_unlock (&ice->priv->lock);
115
116   return NULL;
117 }
118
119 static void
120 _start_thread (GstWebRTCICE * ice)
121 {
122   g_mutex_lock (&ice->priv->lock);
123   ice->priv->thread = g_thread_new (GST_OBJECT_NAME (ice),
124       (GThreadFunc) _gst_nice_thread, ice);
125
126   while (!ice->priv->loop)
127     g_cond_wait (&ice->priv->cond, &ice->priv->lock);
128   g_mutex_unlock (&ice->priv->lock);
129 }
130
131 static void
132 _stop_thread (GstWebRTCICE * ice)
133 {
134   g_mutex_lock (&ice->priv->lock);
135   g_main_loop_quit (ice->priv->loop);
136   while (ice->priv->loop)
137     g_cond_wait (&ice->priv->cond, &ice->priv->lock);
138   g_mutex_unlock (&ice->priv->lock);
139
140   g_thread_unref (ice->priv->thread);
141 }
142
143 struct NiceStreamItem
144 {
145   guint session_id;
146   guint nice_stream_id;
147   GstWebRTCICEStream *stream;
148 };
149
150 /* TRUE to continue, FALSE to stop */
151 typedef gboolean (*NiceStreamItemForeachFunc) (struct NiceStreamItem * item,
152     gpointer user_data);
153
154 static void
155 _nice_stream_item_foreach (GstWebRTCICE * ice, NiceStreamItemForeachFunc func,
156     gpointer data)
157 {
158   int i, len;
159
160   len = ice->priv->nice_stream_map->len;
161   for (i = 0; i < len; i++) {
162     struct NiceStreamItem *item =
163         &g_array_index (ice->priv->nice_stream_map, struct NiceStreamItem,
164         i);
165
166     if (!func (item, data))
167       break;
168   }
169 }
170
171 /* TRUE for match, FALSE otherwise */
172 typedef gboolean (*NiceStreamItemFindFunc) (struct NiceStreamItem * item,
173     gpointer user_data);
174
175 struct nice_find
176 {
177   NiceStreamItemFindFunc func;
178   gpointer data;
179   struct NiceStreamItem *ret;
180 };
181
182 static gboolean
183 _find_nice_item (struct NiceStreamItem *item, gpointer user_data)
184 {
185   struct nice_find *f = user_data;
186   if (f->func (item, f->data)) {
187     f->ret = item;
188     return FALSE;
189   }
190   return TRUE;
191 }
192
193 static struct NiceStreamItem *
194 _nice_stream_item_find (GstWebRTCICE * ice, NiceStreamItemFindFunc func,
195     gpointer data)
196 {
197   struct nice_find f;
198
199   f.func = func;
200   f.data = data;
201   f.ret = NULL;
202
203   _nice_stream_item_foreach (ice, _find_nice_item, &f);
204
205   return f.ret;
206 }
207
208 #define NICE_MATCH_INIT { -1, -1, NULL }
209
210 static gboolean
211 _match (struct NiceStreamItem *item, struct NiceStreamItem *m)
212 {
213   if (m->session_id != -1 && m->session_id != item->session_id)
214     return FALSE;
215   if (m->nice_stream_id != -1 && m->nice_stream_id != item->nice_stream_id)
216     return FALSE;
217   if (m->stream != NULL && m->stream != item->stream)
218     return FALSE;
219
220   return TRUE;
221 }
222
223 static struct NiceStreamItem *
224 _find_item (GstWebRTCICE * ice, guint session_id, guint nice_stream_id,
225     GstWebRTCICEStream * stream)
226 {
227   struct NiceStreamItem m = NICE_MATCH_INIT;
228
229   m.session_id = session_id;
230   m.nice_stream_id = nice_stream_id;
231   m.stream = stream;
232
233   return _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
234 }
235
236 static struct NiceStreamItem *
237 _create_nice_stream_item (GstWebRTCICE * ice, guint session_id)
238 {
239   struct NiceStreamItem item;
240
241   item.session_id = session_id;
242   item.nice_stream_id = nice_agent_add_stream (ice->priv->nice_agent, 1);
243   item.stream = gst_webrtc_ice_stream_new (ice, item.nice_stream_id);
244   g_array_append_val (ice->priv->nice_stream_map, item);
245
246   return _find_item (ice, item.session_id, item.nice_stream_id, item.stream);
247 }
248
249 static void
250 _parse_userinfo (const gchar * userinfo, gchar ** user, gchar ** pass)
251 {
252   const gchar *colon;
253
254   if (!userinfo) {
255     *user = NULL;
256     *pass = NULL;
257     return;
258   }
259
260   colon = g_strstr_len (userinfo, -1, ":");
261   if (!colon) {
262     *user = g_uri_unescape_string (userinfo, NULL);
263     *pass = NULL;
264     return;
265   }
266
267   /* Check that the first occurence is also the last occurence */
268   if (colon != g_strrstr (userinfo, ":"))
269     GST_WARNING ("userinfo %s contains more than one ':', will assume that the "
270         "first ':' delineates user:pass. You should escape the user and pass "
271         "before adding to the URI.", userinfo);
272
273   *user = g_uri_unescape_segment (userinfo, colon, NULL);
274   *pass = g_uri_unescape_string (&colon[1], NULL);
275 }
276
277 static gchar *
278 _resolve_host (GstWebRTCICE * ice, const gchar * host)
279 {
280   GResolver *resolver = g_resolver_get_default ();
281   GError *error = NULL;
282   GInetAddress *addr;
283   GList *addresses;
284   gchar *address;
285
286   GST_DEBUG_OBJECT (ice, "Resolving host %s", host);
287
288   if (!(addresses = g_resolver_lookup_by_name (resolver, host, NULL, &error))) {
289     GST_ERROR ("%s", error->message);
290     g_clear_error (&error);
291     return NULL;
292   }
293
294   GST_DEBUG_OBJECT (ice, "Resolved %d addresses for host %s",
295       g_list_length (addresses), host);
296
297   /* XXX: only the first address is used */
298   addr = addresses->data;
299   address = g_inet_address_to_string (addr);
300   g_resolver_free_addresses (addresses);
301
302   return address;
303 }
304
305 static void
306 _add_turn_server (GstWebRTCICE * ice, struct NiceStreamItem *item,
307     GstUri * turn_server)
308 {
309   gboolean ret;
310   gchar *user, *pass;
311   const gchar *host, *userinfo, *transport, *scheme;
312   NiceRelayType relays[4] = { 0, };
313   int i, relay_n = 0;
314   gchar *ip = NULL;
315
316   host = gst_uri_get_host (turn_server);
317   if (!host) {
318     GST_ERROR_OBJECT (ice, "Turn server has no host");
319     goto out;
320   }
321   ip = _resolve_host (ice, host);
322   if (!ip) {
323     GST_ERROR_OBJECT (ice, "Failed to resolve turn server '%s'", host);
324     goto out;
325   }
326
327   /* Set the resolved IP as the host since that's what libnice wants */
328   gst_uri_set_host (turn_server, ip);
329
330   scheme = gst_uri_get_scheme (turn_server);
331   transport = gst_uri_get_query_value (turn_server, "transport");
332   userinfo = gst_uri_get_userinfo (turn_server);
333   _parse_userinfo (userinfo, &user, &pass);
334
335   if (g_strcmp0 (scheme, "turns") == 0) {
336     relays[relay_n++] = NICE_RELAY_TYPE_TURN_TLS;
337   } else if (g_strcmp0 (scheme, "turn") == 0) {
338     if (!transport || g_strcmp0 (transport, "udp") == 0)
339       relays[relay_n++] = NICE_RELAY_TYPE_TURN_UDP;
340     if (!transport || g_strcmp0 (transport, "tcp") == 0)
341       relays[relay_n++] = NICE_RELAY_TYPE_TURN_TCP;
342   }
343   g_assert (relay_n < G_N_ELEMENTS (relays));
344
345   for (i = 0; i < relay_n; i++) {
346     ret = nice_agent_set_relay_info (ice->priv->nice_agent,
347         item->nice_stream_id, NICE_COMPONENT_TYPE_RTP,
348         gst_uri_get_host (turn_server), gst_uri_get_port (turn_server),
349         user, pass, relays[i]);
350     if (!ret) {
351       gchar *uri = gst_uri_to_string (turn_server);
352       GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri);
353       g_free (uri);
354       break;
355     }
356   }
357   g_free (user);
358   g_free (pass);
359
360 out:
361   g_free (ip);
362 }
363
364 typedef struct
365 {
366   GstWebRTCICE *ice;
367   struct NiceStreamItem *item;
368 } AddTurnServerData;
369
370 static void
371 _add_turn_server_func (const gchar * uri, GstUri * turn_server,
372     AddTurnServerData * data)
373 {
374   _add_turn_server (data->ice, data->item, turn_server);
375 }
376
377 static void
378 _add_stun_server (GstWebRTCICE * ice, GstUri * stun_server)
379 {
380   const gchar *msg = "must be of the form stun://<host>:<port>";
381   const gchar *host;
382   gchar *s = NULL;
383   gchar *ip = NULL;
384   guint port;
385
386   s = gst_uri_to_string (stun_server);
387   GST_DEBUG_OBJECT (ice, "adding stun server, %s", s);
388
389   host = gst_uri_get_host (stun_server);
390   if (!host) {
391     GST_ERROR_OBJECT (ice, "Stun server '%s' has no host, %s", s, msg);
392     goto out;
393   }
394
395   port = gst_uri_get_port (stun_server);
396   if (port == GST_URI_NO_PORT) {
397     GST_INFO_OBJECT (ice, "Stun server '%s' has no port, assuming 3478", s);
398     port = 3478;
399     gst_uri_set_port (stun_server, port);
400   }
401
402   ip = _resolve_host (ice, host);
403   if (!ip) {
404     GST_ERROR_OBJECT (ice, "Failed to resolve stun server '%s'", host);
405     goto out;
406   }
407
408   g_object_set (ice->priv->nice_agent, "stun-server", ip,
409       "stun-server-port", port, NULL);
410
411 out:
412   g_free (s);
413   g_free (ip);
414 }
415
416 GstWebRTCICEStream *
417 gst_webrtc_ice_add_stream (GstWebRTCICE * ice, guint session_id)
418 {
419   struct NiceStreamItem m = NICE_MATCH_INIT;
420   struct NiceStreamItem *item;
421   AddTurnServerData add_data;
422
423   m.session_id = session_id;
424   item = _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
425   if (item) {
426     GST_ERROR_OBJECT (ice, "stream already added with session_id=%u",
427         session_id);
428     return 0;
429   }
430
431   if (ice->stun_server) {
432     _add_stun_server (ice, ice->stun_server);
433   }
434
435   item = _create_nice_stream_item (ice, session_id);
436
437   if (ice->turn_server) {
438     _add_turn_server (ice, item, ice->turn_server);
439   }
440
441   add_data.ice = ice;
442   add_data.item = item;
443
444   g_hash_table_foreach (ice->turn_servers, (GHFunc) _add_turn_server_func,
445       &add_data);
446
447   return item->stream;
448 }
449
450 static void
451 _on_new_candidate (NiceAgent * agent, NiceCandidate * candidate,
452     GstWebRTCICE * ice)
453 {
454   struct NiceStreamItem *item;
455   gchar *attr;
456
457   item = _find_item (ice, -1, candidate->stream_id, NULL);
458   if (!item) {
459     GST_WARNING_OBJECT (ice, "received signal for non-existent stream %u",
460         candidate->stream_id);
461     return;
462   }
463
464   if (!candidate->username || !candidate->password) {
465     gboolean got_credentials;
466     gchar *ufrag, *password;
467
468     got_credentials = nice_agent_get_local_credentials (ice->priv->nice_agent,
469         candidate->stream_id, &ufrag, &password);
470     g_warn_if_fail (got_credentials);
471
472     if (!candidate->username)
473       candidate->username = ufrag;
474     else
475       g_free (ufrag);
476
477     if (!candidate->password)
478       candidate->password = password;
479     else
480       g_free (password);
481   }
482
483   attr = nice_agent_generate_local_candidate_sdp (agent, candidate);
484
485   if (ice->priv->on_candidate)
486     ice->priv->on_candidate (ice, item->session_id, attr,
487         ice->priv->on_candidate_data);
488
489   g_free (attr);
490 }
491
492 GstWebRTCICETransport *
493 gst_webrtc_ice_find_transport (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
494     GstWebRTCICEComponent component)
495 {
496   struct NiceStreamItem *item;
497
498   item = _find_item (ice, -1, -1, stream);
499   g_return_val_if_fail (item != NULL, NULL);
500
501   return gst_webrtc_ice_stream_find_transport (item->stream, component);
502 }
503
504 #if 0
505 /* TODO don't rely on libnice to (de)serialize candidates */
506 static NiceCandidateType
507 _candidate_type_from_string (const gchar * s)
508 {
509   if (g_strcmp0 (s, "host") == 0) {
510     return NICE_CANDIDATE_TYPE_HOST;
511   } else if (g_strcmp0 (s, "srflx") == 0) {
512     return NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
513   } else if (g_strcmp0 (s, "prflx") == 0) {     /* FIXME: is the right string? */
514     return NICE_CANDIDATE_TYPE_PEER_REFLEXIVE;
515   } else if (g_strcmp0 (s, "relay") == 0) {
516     return NICE_CANDIDATE_TYPE_RELAY;
517   } else {
518     g_assert_not_reached ();
519     return 0;
520   }
521 }
522
523 static const gchar *
524 _candidate_type_to_string (NiceCandidateType type)
525 {
526   switch (type) {
527     case NICE_CANDIDATE_TYPE_HOST:
528       return "host";
529     case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
530       return "srflx";
531     case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
532       return "prflx";
533     case NICE_CANDIDATE_TYPE_RELAY:
534       return "relay";
535     default:
536       g_assert_not_reached ();
537       return NULL;
538   }
539 }
540
541 static NiceCandidateTransport
542 _candidate_transport_from_string (const gchar * s)
543 {
544   if (g_strcmp0 (s, "UDP") == 0) {
545     return NICE_CANDIDATE_TRANSPORT_UDP;
546   } else if (g_strcmp0 (s, "TCP tcptype") == 0) {
547     return NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE;
548   } else if (g_strcmp0 (s, "tcp-passive") == 0) {       /* FIXME: is the right string? */
549     return NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE;
550   } else if (g_strcmp0 (s, "tcp-so") == 0) {
551     return NICE_CANDIDATE_TRANSPORT_TCP_SO;
552   } else {
553     g_assert_not_reached ();
554     return 0;
555   }
556 }
557
558 static const gchar *
559 _candidate_type_to_string (NiceCandidateType type)
560 {
561   switch (type) {
562     case NICE_CANDIDATE_TYPE_HOST:
563       return "host";
564     case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
565       return "srflx";
566     case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
567       return "prflx";
568     case NICE_CANDIDATE_TYPE_RELAY:
569       return "relay";
570     default:
571       g_assert_not_reached ();
572       return NULL;
573   }
574 }
575 #endif
576
577 /* parse the address for possible resolution */
578 static gboolean
579 get_candidate_address (const gchar * candidate, gchar ** prefix,
580     gchar ** address, gchar ** postfix)
581 {
582   char **tokens = NULL;
583
584   if (!g_str_has_prefix (candidate, "a=candidate:")) {
585     GST_ERROR ("candidate \"%s\" does not start with \"a=candidate:\"",
586         candidate);
587     goto failure;
588   }
589
590   if (!(tokens = g_strsplit (candidate, " ", 6))) {
591     GST_ERROR ("candidate \"%s\" could not be tokenized", candidate);
592     goto failure;
593   }
594
595   if (g_strv_length (tokens) < 6) {
596     GST_ERROR ("candidate \"%s\" tokenization resulted in not enough tokens",
597         candidate);
598     goto failure;
599   }
600
601   if (address)
602     *address = g_strdup (tokens[4]);
603   tokens[4] = NULL;
604   if (prefix)
605     *prefix = g_strjoinv (" ", tokens);
606   if (postfix)
607     *postfix = g_strdup (tokens[5]);
608
609   g_strfreev (tokens);
610   return TRUE;
611
612 failure:
613   if (tokens)
614     g_strfreev (tokens);
615   return FALSE;
616 }
617
618 /* candidate must start with "a=candidate:" or be NULL*/
619 void
620 gst_webrtc_ice_add_candidate (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
621     const gchar * candidate)
622 {
623   struct NiceStreamItem *item;
624   NiceCandidate *cand;
625   GSList *candidates = NULL;
626
627   item = _find_item (ice, -1, -1, stream);
628   g_return_if_fail (item != NULL);
629
630   if (candidate == NULL) {
631     nice_agent_peer_candidate_gathering_done (ice->priv->nice_agent,
632         item->nice_stream_id);
633     return;
634   }
635
636   cand =
637       nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent,
638       item->nice_stream_id, candidate);
639   if (!cand) {
640     /* might be a .local candidate */
641     char *prefix = NULL, *address = NULL, *postfix = NULL;
642     char *new_addr, *new_candidate;
643     char *new_candv[4] = { NULL, };
644
645     if (!get_candidate_address (candidate, &prefix, &address, &postfix)) {
646       GST_WARNING_OBJECT (ice, "Failed to retrieve address from candidate %s",
647           candidate);
648       goto fail;
649     }
650
651     if (!g_str_has_suffix (address, ".local")) {
652       GST_WARNING_OBJECT (ice, "candidate address \'%s\' does not end "
653           "with \'.local\'", address);
654       goto fail;
655     }
656
657     /* FIXME: async */
658     if (!(new_addr = _resolve_host (ice, address))) {
659       GST_WARNING_OBJECT (ice, "Failed to resolve %s", address);
660       goto fail;
661     }
662
663     new_candv[0] = prefix;
664     new_candv[1] = new_addr;
665     new_candv[2] = postfix;
666     new_candv[3] = NULL;
667     new_candidate = g_strjoinv (" ", new_candv);
668
669     GST_DEBUG_OBJECT (ice, "resolved to candidate %s", new_candidate);
670
671     cand =
672         nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent,
673         item->nice_stream_id, new_candidate);
674     g_free (new_candidate);
675     if (!cand) {
676       GST_WARNING_OBJECT (ice, "Could not parse candidate \'%s\'",
677           new_candidate);
678       goto fail;
679     }
680
681     g_free (prefix);
682     g_free (new_addr);
683     g_free (postfix);
684
685     if (0) {
686     fail:
687       g_free (prefix);
688       g_free (address);
689       g_free (postfix);
690       return;
691     }
692   }
693
694   if (cand->component_id == 2) {
695     /* we only support rtcp-mux so rtcp candidates are useless for us */
696     GST_INFO_OBJECT (ice, "Dropping RTCP candidate %s", candidate);
697     nice_candidate_free (cand);
698     return;
699   }
700
701   candidates = g_slist_append (candidates, cand);
702
703   nice_agent_set_remote_candidates (ice->priv->nice_agent, item->nice_stream_id,
704       cand->component_id, candidates);
705
706   g_slist_free (candidates);
707   nice_candidate_free (cand);
708 }
709
710 gboolean
711 gst_webrtc_ice_set_remote_credentials (GstWebRTCICE * ice,
712     GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
713 {
714   struct NiceStreamItem *item;
715
716   g_return_val_if_fail (ufrag != NULL, FALSE);
717   g_return_val_if_fail (pwd != NULL, FALSE);
718   item = _find_item (ice, -1, -1, stream);
719   g_return_val_if_fail (item != NULL, FALSE);
720
721   GST_DEBUG_OBJECT (ice, "Setting remote ICE credentials on "
722       "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
723
724   nice_agent_set_remote_credentials (ice->priv->nice_agent,
725       item->nice_stream_id, ufrag, pwd);
726
727   return TRUE;
728 }
729
730 gboolean
731 gst_webrtc_ice_add_turn_server (GstWebRTCICE * ice, const gchar * uri)
732 {
733   gboolean ret = FALSE;
734   GstUri *valid_uri;
735
736   if (!(valid_uri = _validate_turn_server (ice, uri)))
737     goto done;
738
739   g_hash_table_insert (ice->turn_servers, g_strdup (uri), valid_uri);
740
741   ret = TRUE;
742
743 done:
744   return ret;
745 }
746
747 static gboolean
748 gst_webrtc_ice_add_local_ip_address (GstWebRTCICE * ice, const gchar * address)
749 {
750   gboolean ret = FALSE;
751   NiceAddress nice_addr;
752
753   nice_address_init (&nice_addr);
754
755   ret = nice_address_set_from_string (&nice_addr, address);
756
757   if (ret) {
758     ret = nice_agent_add_local_address (ice->priv->nice_agent, &nice_addr);
759     if (!ret) {
760       GST_ERROR_OBJECT (ice, "Failed to add local address to NiceAgent");
761     }
762   } else {
763     GST_ERROR_OBJECT (ice, "Failed to initialize NiceAddress [%s]", address);
764   }
765
766   return ret;
767 }
768
769 gboolean
770 gst_webrtc_ice_set_local_credentials (GstWebRTCICE * ice,
771     GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
772 {
773   struct NiceStreamItem *item;
774
775   g_return_val_if_fail (ufrag != NULL, FALSE);
776   g_return_val_if_fail (pwd != NULL, FALSE);
777   item = _find_item (ice, -1, -1, stream);
778   g_return_val_if_fail (item != NULL, FALSE);
779
780   GST_DEBUG_OBJECT (ice, "Setting local ICE credentials on "
781       "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
782
783   nice_agent_set_local_credentials (ice->priv->nice_agent, item->nice_stream_id,
784       ufrag, pwd);
785
786   return TRUE;
787 }
788
789 gboolean
790 gst_webrtc_ice_gather_candidates (GstWebRTCICE * ice,
791     GstWebRTCICEStream * stream)
792 {
793   struct NiceStreamItem *item;
794
795   item = _find_item (ice, -1, -1, stream);
796   g_return_val_if_fail (item != NULL, FALSE);
797
798   GST_DEBUG_OBJECT (ice, "gather candidates for stream %u",
799       item->nice_stream_id);
800
801   return gst_webrtc_ice_stream_gather_candidates (stream);
802 }
803
804 void
805 gst_webrtc_ice_set_is_controller (GstWebRTCICE * ice, gboolean controller)
806 {
807   g_object_set (G_OBJECT (ice->priv->nice_agent), "controlling-mode",
808       controller, NULL);
809 }
810
811 gboolean
812 gst_webrtc_ice_get_is_controller (GstWebRTCICE * ice)
813 {
814   gboolean ret;
815   g_object_get (G_OBJECT (ice->priv->nice_agent), "controlling-mode",
816       &ret, NULL);
817   return ret;
818 }
819
820 void
821 gst_webrtc_ice_set_force_relay (GstWebRTCICE * ice, gboolean force_relay)
822 {
823   g_object_set (G_OBJECT (ice->priv->nice_agent), "force-relay", force_relay,
824       NULL);
825 }
826
827 void
828 gst_webrtc_ice_set_on_ice_candidate (GstWebRTCICE * ice,
829     GstWebRTCIceOnCandidateFunc func, gpointer user_data, GDestroyNotify notify)
830 {
831   if (ice->priv->on_candidate_notify)
832     ice->priv->on_candidate_notify (ice->priv->on_candidate_data);
833   ice->priv->on_candidate = NULL;
834
835   ice->priv->on_candidate = func;
836   ice->priv->on_candidate_data = user_data;
837   ice->priv->on_candidate_notify = notify;
838 }
839
840 void
841 gst_webrtc_ice_set_tos (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
842     guint tos)
843 {
844   struct NiceStreamItem *item;
845
846   item = _find_item (ice, -1, -1, stream);
847   g_return_if_fail (item != NULL);
848
849   nice_agent_set_stream_tos (ice->priv->nice_agent, item->nice_stream_id, tos);
850 }
851
852 static void
853 _clear_ice_stream (struct NiceStreamItem *item)
854 {
855   if (!item)
856     return;
857
858   if (item->stream) {
859     g_signal_handlers_disconnect_by_data (item->stream->ice->priv->nice_agent,
860         item->stream);
861     gst_object_unref (item->stream);
862   }
863 }
864
865 static GstUri *
866 _validate_turn_server (GstWebRTCICE * ice, const gchar * s)
867 {
868   GstUri *uri = gst_uri_from_string_escaped (s);
869   const gchar *userinfo, *scheme;
870   GList *keys = NULL, *l;
871   gchar *user = NULL, *pass = NULL;
872   gboolean turn_tls = FALSE;
873   guint port;
874
875   GST_DEBUG_OBJECT (ice, "validating turn server, %s", s);
876
877   if (!uri) {
878     GST_ERROR_OBJECT (ice, "Could not parse turn server '%s'", s);
879     return NULL;
880   }
881
882   scheme = gst_uri_get_scheme (uri);
883   if (g_strcmp0 (scheme, "turn") == 0) {
884   } else if (g_strcmp0 (scheme, "turns") == 0) {
885     turn_tls = TRUE;
886   } else {
887     GST_ERROR_OBJECT (ice, "unknown scheme '%s'", scheme);
888     goto out;
889   }
890
891   keys = gst_uri_get_query_keys (uri);
892   for (l = keys; l; l = l->next) {
893     gchar *key = l->data;
894
895     if (g_strcmp0 (key, "transport") == 0) {
896       const gchar *transport = gst_uri_get_query_value (uri, "transport");
897       if (!transport) {
898       } else if (g_strcmp0 (transport, "udp") == 0) {
899       } else if (g_strcmp0 (transport, "tcp") == 0) {
900       } else {
901         GST_ERROR_OBJECT (ice, "unknown transport value, '%s'", transport);
902         goto out;
903       }
904     } else {
905       GST_ERROR_OBJECT (ice, "unknown query key, '%s'", key);
906       goto out;
907     }
908   }
909
910   /* TODO: Implement error checking similar to the stun server below */
911   userinfo = gst_uri_get_userinfo (uri);
912   _parse_userinfo (userinfo, &user, &pass);
913   if (!user) {
914     GST_ERROR_OBJECT (ice, "No username specified in '%s'", s);
915     goto out;
916   }
917   if (!pass) {
918     GST_ERROR_OBJECT (ice, "No password specified in '%s'", s);
919     goto out;
920   }
921
922   port = gst_uri_get_port (uri);
923
924   if (port == GST_URI_NO_PORT) {
925     if (turn_tls) {
926       gst_uri_set_port (uri, 5349);
927     } else {
928       gst_uri_set_port (uri, 3478);
929     }
930   }
931
932 out:
933   g_list_free (keys);
934   g_free (user);
935   g_free (pass);
936
937   return uri;
938 }
939
940 void
941 gst_webrtc_ice_set_stun_server (GstWebRTCICE * ice, const gchar * uri_s)
942 {
943   GstUri *uri = gst_uri_from_string_escaped (uri_s);
944   const gchar *msg = "must be of the form stun://<host>:<port>";
945
946   GST_DEBUG_OBJECT (ice, "setting stun server, %s", uri_s);
947
948   if (!uri) {
949     GST_ERROR_OBJECT (ice, "Couldn't parse stun server '%s', %s", uri_s, msg);
950     return;
951   }
952
953   if (ice->stun_server)
954     gst_uri_unref (ice->stun_server);
955   ice->stun_server = uri;
956 }
957
958 gchar *
959 gst_webrtc_ice_get_stun_server (GstWebRTCICE * ice)
960 {
961   if (ice->stun_server)
962     return gst_uri_to_string (ice->stun_server);
963   else
964     return NULL;
965 }
966
967 void
968 gst_webrtc_ice_set_turn_server (GstWebRTCICE * ice, const gchar * uri_s)
969 {
970   GstUri *uri = _validate_turn_server (ice, uri_s);
971
972   if (uri) {
973     if (ice->turn_server)
974       gst_uri_unref (ice->turn_server);
975     ice->turn_server = uri;
976   }
977 }
978
979 gchar *
980 gst_webrtc_ice_get_turn_server (GstWebRTCICE * ice)
981 {
982   if (ice->turn_server)
983     return gst_uri_to_string (ice->turn_server);
984   else
985     return NULL;
986 }
987
988 static void
989 gst_webrtc_ice_set_property (GObject * object, guint prop_id,
990     const GValue * value, GParamSpec * pspec)
991 {
992   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
993
994   switch (prop_id) {
995     case PROP_ICE_TCP:
996       g_object_set_property (G_OBJECT (ice->priv->nice_agent),
997           "ice-tcp", value);
998       break;
999     case PROP_ICE_UDP:
1000       g_object_set_property (G_OBJECT (ice->priv->nice_agent),
1001           "ice-udp", value);
1002       break;
1003
1004     case PROP_MIN_RTP_PORT:
1005       ice->min_rtp_port = g_value_get_uint (value);
1006       if (ice->min_rtp_port > ice->max_rtp_port)
1007         g_warning ("Set min-rtp-port to %u which is larger than"
1008             " max-rtp-port %u", ice->min_rtp_port, ice->max_rtp_port);
1009       break;
1010
1011     case PROP_MAX_RTP_PORT:
1012       ice->max_rtp_port = g_value_get_uint (value);
1013       if (ice->min_rtp_port > ice->max_rtp_port)
1014         g_warning ("Set max-rtp-port to %u which is smaller than"
1015             " min-rtp-port %u", ice->max_rtp_port, ice->min_rtp_port);
1016       break;
1017
1018     default:
1019       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1020       break;
1021   }
1022 }
1023
1024 static void
1025 gst_webrtc_ice_get_property (GObject * object, guint prop_id,
1026     GValue * value, GParamSpec * pspec)
1027 {
1028   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1029
1030   switch (prop_id) {
1031     case PROP_AGENT:
1032       g_value_set_object (value, ice->priv->nice_agent);
1033       break;
1034     case PROP_ICE_TCP:
1035       g_object_get_property (G_OBJECT (ice->priv->nice_agent),
1036           "ice-tcp", value);
1037       break;
1038     case PROP_ICE_UDP:
1039       g_object_get_property (G_OBJECT (ice->priv->nice_agent),
1040           "ice-udp", value);
1041       break;
1042
1043     case PROP_MIN_RTP_PORT:
1044       g_value_set_uint (value, ice->min_rtp_port);
1045       break;
1046
1047     case PROP_MAX_RTP_PORT:
1048       g_value_set_uint (value, ice->max_rtp_port);
1049       break;
1050
1051     default:
1052       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1053       break;
1054   }
1055 }
1056
1057 static void
1058 gst_webrtc_ice_finalize (GObject * object)
1059 {
1060   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1061
1062   g_signal_handlers_disconnect_by_data (ice->priv->nice_agent, ice);
1063
1064   _stop_thread (ice);
1065
1066   if (ice->priv->on_candidate_notify)
1067     ice->priv->on_candidate_notify (ice->priv->on_candidate_data);
1068   ice->priv->on_candidate = NULL;
1069   ice->priv->on_candidate_notify = NULL;
1070
1071   if (ice->turn_server)
1072     gst_uri_unref (ice->turn_server);
1073   if (ice->stun_server)
1074     gst_uri_unref (ice->stun_server);
1075
1076   g_mutex_clear (&ice->priv->lock);
1077   g_cond_clear (&ice->priv->cond);
1078
1079   g_array_free (ice->priv->nice_stream_map, TRUE);
1080
1081   g_object_unref (ice->priv->nice_agent);
1082
1083   g_hash_table_unref (ice->turn_servers);
1084
1085   G_OBJECT_CLASS (parent_class)->finalize (object);
1086 }
1087
1088 static void
1089 gst_webrtc_ice_constructed (GObject * object)
1090 {
1091   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
1092   NiceAgentOption options = 0;
1093
1094   _start_thread (ice);
1095
1096   options |= NICE_AGENT_OPTION_ICE_TRICKLE;
1097   options |= NICE_AGENT_OPTION_REGULAR_NOMINATION;
1098
1099   ice->priv->nice_agent = nice_agent_new_full (ice->priv->main_context,
1100       NICE_COMPATIBILITY_RFC5245, options);
1101   g_signal_connect (ice->priv->nice_agent, "new-candidate-full",
1102       G_CALLBACK (_on_new_candidate), ice);
1103
1104   G_OBJECT_CLASS (parent_class)->constructed (object);
1105 }
1106
1107 static void
1108 gst_webrtc_ice_class_init (GstWebRTCICEClass * klass)
1109 {
1110   GObjectClass *gobject_class = (GObjectClass *) klass;
1111
1112   gobject_class->constructed = gst_webrtc_ice_constructed;
1113   gobject_class->get_property = gst_webrtc_ice_get_property;
1114   gobject_class->set_property = gst_webrtc_ice_set_property;
1115   gobject_class->finalize = gst_webrtc_ice_finalize;
1116
1117   g_object_class_install_property (gobject_class,
1118       PROP_AGENT,
1119       g_param_spec_object ("agent", "ICE agent",
1120           "ICE agent in use by this object. WARNING! Accessing this property "
1121           "may have disastrous consequences for the operation of webrtcbin. "
1122           "Other ICE implementations may not have the same interface.",
1123           NICE_TYPE_AGENT, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
1124
1125   g_object_class_install_property (gobject_class,
1126       PROP_ICE_TCP,
1127       g_param_spec_boolean ("ice-tcp", "ICE TCP",
1128           "Whether the agent should use ICE-TCP when gathering candidates",
1129           TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1130
1131   g_object_class_install_property (gobject_class,
1132       PROP_ICE_UDP,
1133       g_param_spec_boolean ("ice-udp", "ICE UDP",
1134           "Whether the agent should use ICE-UDP when gathering candidates",
1135           TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1136
1137   /**
1138    * GstWebRTCICE:min-rtp-port:
1139    *
1140    * Minimum port for local rtp port range.
1141    * min-rtp-port must be <= max-rtp-port
1142    *
1143    * Since: 1.20
1144    */
1145   g_object_class_install_property (gobject_class,
1146       PROP_MIN_RTP_PORT,
1147       g_param_spec_uint ("min-rtp-port", "ICE RTP candidate min port",
1148           "Minimum port for local rtp port range. "
1149           "min-rtp-port must be <= max-rtp-port",
1150           0, 65535, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
1151
1152   /**
1153    * GstWebRTCICE:max-rtp-port:
1154    *
1155    * Maximum port for local rtp port range.
1156    * min-rtp-port must be <= max-rtp-port
1157    *
1158    * Since: 1.20
1159    */
1160   g_object_class_install_property (gobject_class,
1161       PROP_MAX_RTP_PORT,
1162       g_param_spec_uint ("max-rtp-port", "ICE RTP candidate max port",
1163           "Maximum port for local rtp port range. "
1164           "max-rtp-port must be >= min-rtp-port",
1165           0, 65535, 65535,
1166           G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS));
1167
1168   /**
1169    * GstWebRTCICE::add-local-ip-address:
1170    * @object: the #GstWebRTCICE
1171    * @address: The local IP address
1172    *
1173    * Add a local IP address to use for ICE candidate gathering.  If none
1174    * are supplied, they will be discovered automatically. Calling this signal
1175    * stops automatic ICE gathering.
1176    *
1177    * Returns: whether the address could be added.
1178    */
1179   gst_webrtc_ice_signals[ADD_LOCAL_IP_ADDRESS_SIGNAL] =
1180       g_signal_new_class_handler ("add-local-ip-address",
1181       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
1182       G_CALLBACK (gst_webrtc_ice_add_local_ip_address), NULL, NULL,
1183       g_cclosure_marshal_generic, G_TYPE_BOOLEAN, 1, G_TYPE_STRING);
1184 }
1185
1186 static void
1187 gst_webrtc_ice_init (GstWebRTCICE * ice)
1188 {
1189   ice->priv = gst_webrtc_ice_get_instance_private (ice);
1190
1191   g_mutex_init (&ice->priv->lock);
1192   g_cond_init (&ice->priv->cond);
1193
1194   ice->turn_servers =
1195       g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
1196       (GDestroyNotify) gst_uri_unref);
1197
1198   ice->priv->nice_stream_map =
1199       g_array_new (FALSE, TRUE, sizeof (struct NiceStreamItem));
1200   g_array_set_clear_func (ice->priv->nice_stream_map,
1201       (GDestroyNotify) _clear_ice_stream);
1202 }
1203
1204 GstWebRTCICE *
1205 gst_webrtc_ice_new (const gchar * name)
1206 {
1207   return g_object_new (GST_TYPE_WEBRTC_ICE, "name", name, NULL);
1208 }