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