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