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