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