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