webrtcbin: an element that handles the transport aspects of webrtc connections
[platform/upstream/gst-plugins-bad.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 #define GST_CAT_DEFAULT gst_webrtc_ice_debug
36 GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
37
38 #define gst_webrtc_ice_parent_class parent_class
39 G_DEFINE_TYPE_WITH_CODE (GstWebRTCICE, gst_webrtc_ice,
40     GST_TYPE_OBJECT,
41     GST_DEBUG_CATEGORY_INIT (gst_webrtc_ice_debug, "webrtcice", 0, "webrtcice");
42     );
43
44 GQuark
45 gst_webrtc_ice_error_quark (void)
46 {
47   return g_quark_from_static_string ("gst-webrtc-ice-error-quark");
48 }
49
50 enum
51 {
52   SIGNAL_0,
53   ON_ICE_CANDIDATE_SIGNAL,
54   ON_ICE_GATHERING_STATE_CHANGE_SIGNAL,
55   LAST_SIGNAL,
56 };
57
58 enum
59 {
60   PROP_0,
61   PROP_ICE_GATHERING_STATE,
62   PROP_STUN_SERVER,
63   PROP_TURN_SERVER,
64   PROP_CONTROLLER,
65   PROP_AGENT,
66 };
67
68 static guint gst_webrtc_ice_signals[LAST_SIGNAL] = { 0 };
69
70 struct _GstWebRTCICEPrivate
71 {
72   NiceAgent *nice_agent;
73
74   GArray *nice_stream_map;
75
76   GThread *thread;
77   GMainContext *main_context;
78   GMainLoop *loop;
79   GMutex lock;
80   GCond cond;
81 };
82
83 static gboolean
84 _unlock_pc_thread (GMutex * lock)
85 {
86   g_mutex_unlock (lock);
87   return G_SOURCE_REMOVE;
88 }
89
90 static gpointer
91 _gst_nice_thread (GstWebRTCICE * ice)
92 {
93   g_mutex_lock (&ice->priv->lock);
94   ice->priv->main_context = g_main_context_new ();
95   ice->priv->loop = g_main_loop_new (ice->priv->main_context, FALSE);
96
97   g_cond_broadcast (&ice->priv->cond);
98   g_main_context_invoke (ice->priv->main_context,
99       (GSourceFunc) _unlock_pc_thread, &ice->priv->lock);
100
101   g_main_loop_run (ice->priv->loop);
102
103   g_mutex_lock (&ice->priv->lock);
104   g_main_context_unref (ice->priv->main_context);
105   ice->priv->main_context = NULL;
106   g_main_loop_unref (ice->priv->loop);
107   ice->priv->loop = NULL;
108   g_cond_broadcast (&ice->priv->cond);
109   g_mutex_unlock (&ice->priv->lock);
110
111   return NULL;
112 }
113
114 static void
115 _start_thread (GstWebRTCICE * ice)
116 {
117   g_mutex_lock (&ice->priv->lock);
118   ice->priv->thread = g_thread_new ("gst-nice-ops",
119       (GThreadFunc) _gst_nice_thread, ice);
120
121   while (!ice->priv->loop)
122     g_cond_wait (&ice->priv->cond, &ice->priv->lock);
123   g_mutex_unlock (&ice->priv->lock);
124 }
125
126 static void
127 _stop_thread (GstWebRTCICE * ice)
128 {
129   g_mutex_lock (&ice->priv->lock);
130   g_main_loop_quit (ice->priv->loop);
131   while (ice->priv->loop)
132     g_cond_wait (&ice->priv->cond, &ice->priv->lock);
133   g_mutex_unlock (&ice->priv->lock);
134
135   g_thread_unref (ice->priv->thread);
136 }
137
138 #if 0
139 static NiceComponentType
140 _webrtc_component_to_nice (GstWebRTCICEComponent comp)
141 {
142   switch (comp) {
143     case GST_WEBRTC_ICE_COMPONENT_RTP:
144       return NICE_COMPONENT_TYPE_RTP;
145     case GST_WEBRTC_ICE_COMPONENT_RTCP:
146       return NICE_COMPONENT_TYPE_RTCP;
147     default:
148       g_assert_not_reached ();
149       return 0;
150   }
151 }
152
153 static GstWebRTCICEComponent
154 _nice_component_to_webrtc (NiceComponentType comp)
155 {
156   switch (comp) {
157     case NICE_COMPONENT_TYPE_RTP:
158       return GST_WEBRTC_ICE_COMPONENT_RTP;
159     case NICE_COMPONENT_TYPE_RTCP:
160       return GST_WEBRTC_ICE_COMPONENT_RTCP;
161     default:
162       g_assert_not_reached ();
163       return 0;
164   }
165 }
166 #endif
167 struct NiceStreamItem
168 {
169   guint session_id;
170   guint nice_stream_id;
171   GstWebRTCICEStream *stream;
172 };
173
174 /* TRUE to continue, FALSE to stop */
175 typedef gboolean (*NiceStreamItemForeachFunc) (struct NiceStreamItem * item,
176     gpointer user_data);
177
178 static void
179 _nice_stream_item_foreach (GstWebRTCICE * ice, NiceStreamItemForeachFunc func,
180     gpointer data)
181 {
182   int i, len;
183
184   len = ice->priv->nice_stream_map->len;
185   for (i = 0; i < len; i++) {
186     struct NiceStreamItem *item =
187         &g_array_index (ice->priv->nice_stream_map, struct NiceStreamItem,
188         i);
189
190     if (!func (item, data))
191       break;
192   }
193 }
194
195 /* TRUE for match, FALSE otherwise */
196 typedef gboolean (*NiceStreamItemFindFunc) (struct NiceStreamItem * item,
197     gpointer user_data);
198
199 struct nice_find
200 {
201   NiceStreamItemFindFunc func;
202   gpointer data;
203   struct NiceStreamItem *ret;
204 };
205
206 static gboolean
207 _find_nice_item (struct NiceStreamItem *item, gpointer user_data)
208 {
209   struct nice_find *f = user_data;
210   if (f->func (item, f->data)) {
211     f->ret = item;
212     return FALSE;
213   }
214   return TRUE;
215 }
216
217 static struct NiceStreamItem *
218 _nice_stream_item_find (GstWebRTCICE * ice, NiceStreamItemFindFunc func,
219     gpointer data)
220 {
221   struct nice_find f;
222
223   f.func = func;
224   f.data = data;
225   f.ret = NULL;
226
227   _nice_stream_item_foreach (ice, _find_nice_item, &f);
228
229   return f.ret;
230 }
231
232 #define NICE_MATCH_INIT { -1, -1, NULL }
233
234 static gboolean
235 _match (struct NiceStreamItem *item, struct NiceStreamItem *m)
236 {
237   if (m->session_id != -1 && m->session_id != item->session_id)
238     return FALSE;
239   if (m->nice_stream_id != -1 && m->nice_stream_id != item->nice_stream_id)
240     return FALSE;
241   if (m->stream != NULL && m->stream != item->stream)
242     return FALSE;
243
244   return TRUE;
245 }
246
247 static struct NiceStreamItem *
248 _find_item (GstWebRTCICE * ice, guint session_id, guint nice_stream_id,
249     GstWebRTCICEStream * stream)
250 {
251   struct NiceStreamItem m = NICE_MATCH_INIT;
252
253   m.session_id = session_id;
254   m.nice_stream_id = nice_stream_id;
255   m.stream = stream;
256
257   return _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
258 }
259
260 static struct NiceStreamItem *
261 _create_nice_stream_item (GstWebRTCICE * ice, guint session_id)
262 {
263   struct NiceStreamItem item;
264
265   item.session_id = session_id;
266   item.nice_stream_id = nice_agent_add_stream (ice->priv->nice_agent, 2);
267   item.stream = gst_webrtc_ice_stream_new (ice, item.nice_stream_id);
268   g_array_append_val (ice->priv->nice_stream_map, item);
269
270   return _find_item (ice, item.session_id, item.nice_stream_id, item.stream);
271 }
272
273 static void
274 _parse_userinfo (const gchar * userinfo, gchar ** user, gchar ** pass)
275 {
276   const gchar *colon;
277
278   if (!userinfo) {
279     *user = NULL;
280     *pass = NULL;
281     return;
282   }
283
284   colon = g_strstr_len (userinfo, -1, ":");
285   if (!colon) {
286     *user = g_strdup (userinfo);
287     *pass = NULL;
288     return;
289   }
290
291   *user = g_strndup (userinfo, colon - userinfo);
292   *pass = g_strdup (&colon[1]);
293 }
294
295 GstWebRTCICEStream *
296 gst_webrtc_ice_add_stream (GstWebRTCICE * ice, guint session_id)
297 {
298   struct NiceStreamItem m = NICE_MATCH_INIT;
299   struct NiceStreamItem *item;
300
301   m.session_id = session_id;
302   item = _nice_stream_item_find (ice, (NiceStreamItemFindFunc) _match, &m);
303   if (item) {
304     GST_ERROR_OBJECT (ice, "stream already added with session_id=%u",
305         session_id);
306     return 0;
307   }
308
309   item = _create_nice_stream_item (ice, session_id);
310
311   if (ice->turn_server) {
312     gboolean ret;
313     gchar *user, *pass;
314     const gchar *userinfo, *transport, *scheme;
315     NiceRelayType relays[4] = { 0, };
316     int i, relay_n = 0;
317
318     scheme = gst_uri_get_scheme (ice->turn_server);
319     transport = gst_uri_get_query_value (ice->turn_server, "transport");
320     userinfo = gst_uri_get_userinfo (ice->turn_server);
321     _parse_userinfo (userinfo, &user, &pass);
322
323     if (g_strcmp0 (scheme, "turns") == 0) {
324       relays[relay_n++] = NICE_RELAY_TYPE_TURN_TLS;
325     } else if (g_strcmp0 (scheme, "turn") == 0) {
326       if (!transport || g_strcmp0 (transport, "udp") == 0)
327         relays[relay_n++] = NICE_RELAY_TYPE_TURN_UDP;
328       if (!transport || g_strcmp0 (transport, "tcp") == 0)
329         relays[relay_n++] = NICE_RELAY_TYPE_TURN_TCP;
330     }
331     g_assert (relay_n < G_N_ELEMENTS (relays));
332
333     for (i = 0; i < relay_n; i++) {
334       ret = nice_agent_set_relay_info (ice->priv->nice_agent,
335           item->nice_stream_id, NICE_COMPONENT_TYPE_RTP,
336           gst_uri_get_host (ice->turn_server),
337           gst_uri_get_port (ice->turn_server), user, pass, relays[i]);
338       if (!ret) {
339         gchar *uri = gst_uri_to_string (ice->turn_server);
340         GST_ERROR_OBJECT (ice, "Failed to set TURN server '%s'", uri);
341         g_free (uri);
342         break;
343       }
344       ret = nice_agent_set_relay_info (ice->priv->nice_agent,
345           item->nice_stream_id, NICE_COMPONENT_TYPE_RTCP,
346           gst_uri_get_host (ice->turn_server),
347           gst_uri_get_port (ice->turn_server), user, pass, relays[i]);
348       if (!ret) {
349         gchar *uri = gst_uri_to_string (ice->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
359   return item->stream;
360 }
361
362 static void
363 _on_new_candidate (NiceAgent * agent, NiceCandidate * candidate,
364     GstWebRTCICE * ice)
365 {
366   struct NiceStreamItem *item;
367   gchar *attr;
368
369   item = _find_item (ice, -1, candidate->stream_id, NULL);
370   if (!item) {
371     GST_WARNING_OBJECT (ice, "received signal for non-existent stream %u",
372         candidate->stream_id);
373     return;
374   }
375
376   if (!candidate->username || !candidate->password) {
377     gboolean got_credentials;
378     gchar *ufrag, *password;
379
380     got_credentials = nice_agent_get_local_credentials (ice->priv->nice_agent,
381         candidate->stream_id, &ufrag, &password);
382     g_warn_if_fail (got_credentials);
383
384     if (!candidate->username)
385       candidate->username = ufrag;
386     else
387       g_free (ufrag);
388
389     if (!candidate->password)
390       candidate->password = password;
391     else
392       g_free (password);
393   }
394
395   attr = nice_agent_generate_local_candidate_sdp (agent, candidate);
396   g_signal_emit (ice, gst_webrtc_ice_signals[ON_ICE_CANDIDATE_SIGNAL],
397       0, item->session_id, attr);
398   g_free (attr);
399 }
400
401 GstWebRTCICETransport *
402 gst_webrtc_ice_find_transport (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
403     GstWebRTCICEComponent component)
404 {
405   struct NiceStreamItem *item;
406
407   item = _find_item (ice, -1, -1, stream);
408   g_return_val_if_fail (item != NULL, NULL);
409
410   return gst_webrtc_ice_stream_find_transport (item->stream, component);
411 }
412
413 #if 0
414 /* TODO don't rely on libnice to (de)serialize candidates */
415 static NiceCandidateType
416 _candidate_type_from_string (const gchar * s)
417 {
418   if (g_strcmp0 (s, "host") == 0) {
419     return NICE_CANDIDATE_TYPE_HOST;
420   } else if (g_strcmp0 (s, "srflx") == 0) {
421     return NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE;
422   } else if (g_strcmp0 (s, "prflx") == 0) {     /* FIXME: is the right string? */
423     return NICE_CANDIDATE_TYPE_PEER_REFLEXIVE;
424   } else if (g_strcmp0 (s, "relay") == 0) {
425     return NICE_CANDIDATE_TYPE_RELAY;
426   } else {
427     g_assert_not_reached ();
428     return 0;
429   }
430 }
431
432 static const gchar *
433 _candidate_type_to_string (NiceCandidateType type)
434 {
435   switch (type) {
436     case NICE_CANDIDATE_TYPE_HOST:
437       return "host";
438     case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
439       return "srflx";
440     case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
441       return "prflx";
442     case NICE_CANDIDATE_TYPE_RELAY:
443       return "relay";
444     default:
445       g_assert_not_reached ();
446       return NULL;
447   }
448 }
449
450 static NiceCandidateTransport
451 _candidate_transport_from_string (const gchar * s)
452 {
453   if (g_strcmp0 (s, "UDP") == 0) {
454     return NICE_CANDIDATE_TRANSPORT_UDP;
455   } else if (g_strcmp0 (s, "TCP tcptype") == 0) {
456     return NICE_CANDIDATE_TRANSPORT_TCP_ACTIVE;
457   } else if (g_strcmp0 (s, "tcp-passive") == 0) {       /* FIXME: is the right string? */
458     return NICE_CANDIDATE_TRANSPORT_TCP_PASSIVE;
459   } else if (g_strcmp0 (s, "tcp-so") == 0) {
460     return NICE_CANDIDATE_TRANSPORT_TCP_SO;
461   } else {
462     g_assert_not_reached ();
463     return 0;
464   }
465 }
466
467 static const gchar *
468 _candidate_type_to_string (NiceCandidateType type)
469 {
470   switch (type) {
471     case NICE_CANDIDATE_TYPE_HOST:
472       return "host";
473     case NICE_CANDIDATE_TYPE_SERVER_REFLEXIVE:
474       return "srflx";
475     case NICE_CANDIDATE_TYPE_PEER_REFLEXIVE:
476       return "prflx";
477     case NICE_CANDIDATE_TYPE_RELAY:
478       return "relay";
479     default:
480       g_assert_not_reached ();
481       return NULL;
482   }
483 }
484 #endif
485
486 /* must start with "a=candidate:" */
487 void
488 gst_webrtc_ice_add_candidate (GstWebRTCICE * ice, GstWebRTCICEStream * stream,
489     const gchar * candidate)
490 {
491   struct NiceStreamItem *item;
492   NiceCandidate *cand;
493   GSList *candidates = NULL;
494
495   item = _find_item (ice, -1, -1, stream);
496   g_return_if_fail (item != NULL);
497
498   cand =
499       nice_agent_parse_remote_candidate_sdp (ice->priv->nice_agent,
500       item->nice_stream_id, candidate);
501   if (!cand) {
502     GST_WARNING_OBJECT (ice, "Could not parse candidate \'%s\'", candidate);
503     return;
504   }
505
506   candidates = g_slist_append (candidates, cand);
507
508   nice_agent_set_remote_candidates (ice->priv->nice_agent, item->nice_stream_id,
509       cand->component_id, candidates);
510
511   g_slist_free (candidates);
512   nice_candidate_free (cand);
513 }
514
515 gboolean
516 gst_webrtc_ice_set_remote_credentials (GstWebRTCICE * ice,
517     GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
518 {
519   struct NiceStreamItem *item;
520
521   g_return_val_if_fail (ufrag != NULL, FALSE);
522   g_return_val_if_fail (pwd != NULL, FALSE);
523   item = _find_item (ice, -1, -1, stream);
524   g_return_val_if_fail (item != NULL, FALSE);
525
526   GST_DEBUG_OBJECT (ice, "Setting remote ICE credentials on "
527       "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
528
529   nice_agent_set_remote_credentials (ice->priv->nice_agent,
530       item->nice_stream_id, ufrag, pwd);
531
532   return TRUE;
533 }
534
535 gboolean
536 gst_webrtc_ice_set_local_credentials (GstWebRTCICE * ice,
537     GstWebRTCICEStream * stream, gchar * ufrag, gchar * pwd)
538 {
539   struct NiceStreamItem *item;
540
541   g_return_val_if_fail (ufrag != NULL, FALSE);
542   g_return_val_if_fail (pwd != NULL, FALSE);
543   item = _find_item (ice, -1, -1, stream);
544   g_return_val_if_fail (item != NULL, FALSE);
545
546   GST_DEBUG_OBJECT (ice, "Setting local ICE credentials on "
547       "ICE stream %u ufrag:%s pwd:%s", item->nice_stream_id, ufrag, pwd);
548
549   nice_agent_set_local_credentials (ice->priv->nice_agent, item->nice_stream_id,
550       ufrag, pwd);
551
552   return TRUE;
553 }
554
555 gboolean
556 gst_webrtc_ice_gather_candidates (GstWebRTCICE * ice,
557     GstWebRTCICEStream * stream)
558 {
559   struct NiceStreamItem *item;
560
561   item = _find_item (ice, -1, -1, stream);
562   g_return_val_if_fail (item != NULL, FALSE);
563
564   GST_DEBUG_OBJECT (ice, "gather candidates for stream %u",
565       item->nice_stream_id);
566
567   return gst_webrtc_ice_stream_gather_candidates (stream);
568 }
569
570 static void
571 _clear_ice_stream (struct NiceStreamItem *item)
572 {
573   if (!item)
574     return;
575
576   if (item->stream) {
577     g_signal_handlers_disconnect_by_data (item->stream->ice->priv->nice_agent,
578         item->stream);
579     gst_object_unref (item->stream);
580   }
581 }
582
583 static gchar *
584 _resolve_host (const gchar * host)
585 {
586   GResolver *resolver = g_resolver_get_default ();
587   GError *error = NULL;
588   GInetAddress *addr;
589   GList *addresses;
590
591   if (!(addresses = g_resolver_lookup_by_name (resolver, host, NULL, &error))) {
592     GST_ERROR ("%s", error->message);
593     g_clear_error (&error);
594     return NULL;
595   }
596
597   /* XXX: only the first address is used */
598   addr = addresses->data;
599
600   return g_inet_address_to_string (addr);
601 }
602
603 static void
604 _set_turn_server (GstWebRTCICE * ice, const gchar * s)
605 {
606   GstUri *uri = gst_uri_from_string (s);
607   const gchar *userinfo, *host, *scheme;
608   GList *keys = NULL, *l;
609   gchar *ip = NULL, *user = NULL, *pass = NULL;
610   gboolean turn_tls = FALSE;
611   guint port;
612
613   GST_DEBUG_OBJECT (ice, "setting turn server, %s", s);
614
615   if (!uri) {
616     GST_ERROR_OBJECT (ice, "Could not parse turn server '%s'", s);
617     return;
618   }
619
620   scheme = gst_uri_get_scheme (uri);
621   if (g_strcmp0 (scheme, "turn") == 0) {
622   } else if (g_strcmp0 (scheme, "turns") == 0) {
623     turn_tls = TRUE;
624   } else {
625     GST_ERROR_OBJECT (ice, "unknown scheme '%s'", scheme);
626     goto out;
627   }
628
629   keys = gst_uri_get_query_keys (uri);
630   for (l = keys; l; l = l->next) {
631     gchar *key = l->data;
632
633     if (g_strcmp0 (key, "transport") == 0) {
634       const gchar *transport = gst_uri_get_query_value (uri, "transport");
635       if (!transport) {
636       } else if (g_strcmp0 (transport, "udp") == 0) {
637       } else if (g_strcmp0 (transport, "tcp") == 0) {
638       } else {
639         GST_ERROR_OBJECT (ice, "unknown transport value, '%s'", transport);
640         goto out;
641       }
642     } else {
643       GST_ERROR_OBJECT (ice, "unknown query key, '%s'", key);
644       goto out;
645     }
646   }
647
648   /* TODO: Implement error checking similar to the stun server below */
649   userinfo = gst_uri_get_userinfo (uri);
650   _parse_userinfo (userinfo, &user, &pass);
651   if (!user) {
652     GST_ERROR_OBJECT (ice, "No username specified in '%s'", s);
653     goto out;
654   }
655   if (!pass) {
656     GST_ERROR_OBJECT (ice, "No password specified in '%s'", s);
657     goto out;
658   }
659
660   host = gst_uri_get_host (uri);
661   if (!host) {
662     GST_ERROR_OBJECT (ice, "Turn server has no host");
663     goto out;
664   }
665   ip = _resolve_host (host);
666   if (!ip) {
667     GST_ERROR_OBJECT (ice, "Failed to resolve turn server '%s'", host);
668     goto out;
669   }
670   port = gst_uri_get_port (uri);
671
672   if (port == GST_URI_NO_PORT) {
673     if (turn_tls) {
674       gst_uri_set_port (uri, 5349);
675     } else {
676       gst_uri_set_port (uri, 3478);
677     }
678   }
679   /* Set the resolved IP as the host since that's what libnice wants */
680   gst_uri_set_host (uri, ip);
681
682   if (ice->turn_server)
683     gst_uri_unref (ice->turn_server);
684   ice->turn_server = uri;
685
686 out:
687   g_list_free (keys);
688   g_free (ip);
689   g_free (user);
690   g_free (pass);
691 }
692
693 static void
694 gst_webrtc_ice_set_property (GObject * object, guint prop_id,
695     const GValue * value, GParamSpec * pspec)
696 {
697   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
698
699   switch (prop_id) {
700     case PROP_STUN_SERVER:{
701       const gchar *s = g_value_get_string (value);
702       GstUri *uri = gst_uri_from_string (s);
703       const gchar *msg = "must be of the form stun://<host>:<port>";
704       const gchar *host;
705       gchar *ip;
706       guint port;
707
708       GST_DEBUG_OBJECT (ice, "setting stun server, %s", s);
709
710       if (!uri) {
711         GST_ERROR_OBJECT (ice, "Couldn't parse stun server '%s', %s", s, msg);
712         return;
713       }
714
715       host = gst_uri_get_host (uri);
716       if (!host) {
717         GST_ERROR_OBJECT (ice, "Stun server '%s' has no host, %s", s, msg);
718         return;
719       }
720       port = gst_uri_get_port (uri);
721       if (port == GST_URI_NO_PORT) {
722         GST_INFO_OBJECT (ice, "Stun server '%s' has no port, assuming 3478", s);
723         port = 3478;
724         gst_uri_set_port (uri, port);
725       }
726
727       ip = _resolve_host (host);
728       if (!ip) {
729         GST_ERROR_OBJECT (ice, "Failed to resolve stun server '%s'", host);
730         return;
731       }
732
733       if (ice->stun_server)
734         gst_uri_unref (ice->stun_server);
735       ice->stun_server = uri;
736
737       g_object_set (ice->priv->nice_agent, "stun-server", ip,
738           "stun-server-port", port, NULL);
739
740       g_free (ip);
741       break;
742     }
743     case PROP_TURN_SERVER:{
744       _set_turn_server (ice, g_value_get_string (value));
745       break;
746     }
747     case PROP_CONTROLLER:
748       g_object_set_property (G_OBJECT (ice->priv->nice_agent),
749           "controlling-mode", value);
750       break;
751     default:
752       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
753       break;
754   }
755 }
756
757 static void
758 gst_webrtc_ice_get_property (GObject * object, guint prop_id,
759     GValue * value, GParamSpec * pspec)
760 {
761   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
762
763   switch (prop_id) {
764     case PROP_STUN_SERVER:
765       if (ice->stun_server)
766         g_value_take_string (value, gst_uri_to_string (ice->stun_server));
767       else
768         g_value_take_string (value, NULL);
769       break;
770     case PROP_TURN_SERVER:
771       if (ice->turn_server)
772         g_value_take_string (value, gst_uri_to_string (ice->turn_server));
773       else
774         g_value_take_string (value, NULL);
775       break;
776     case PROP_CONTROLLER:
777       g_object_get_property (G_OBJECT (ice->priv->nice_agent),
778           "controlling-mode", value);
779       break;
780     case PROP_AGENT:
781       g_value_set_object (value, ice->priv->nice_agent);
782       break;
783     default:
784       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
785       break;
786   }
787 }
788
789 static void
790 gst_webrtc_ice_finalize (GObject * object)
791 {
792   GstWebRTCICE *ice = GST_WEBRTC_ICE (object);
793
794   g_signal_handlers_disconnect_by_data (ice->priv->nice_agent, ice);
795
796   _stop_thread (ice);
797
798   if (ice->turn_server)
799     gst_uri_unref (ice->turn_server);
800   if (ice->stun_server)
801     gst_uri_unref (ice->stun_server);
802
803   g_mutex_clear (&ice->priv->lock);
804   g_cond_clear (&ice->priv->cond);
805
806   g_array_free (ice->priv->nice_stream_map, TRUE);
807
808   g_object_unref (ice->priv->nice_agent);
809
810   G_OBJECT_CLASS (parent_class)->finalize (object);
811 }
812
813 static void
814 gst_webrtc_ice_class_init (GstWebRTCICEClass * klass)
815 {
816   GObjectClass *gobject_class = (GObjectClass *) klass;
817
818   g_type_class_add_private (klass, sizeof (GstWebRTCICEPrivate));
819
820   gobject_class->get_property = gst_webrtc_ice_get_property;
821   gobject_class->set_property = gst_webrtc_ice_set_property;
822   gobject_class->finalize = gst_webrtc_ice_finalize;
823
824   g_object_class_install_property (gobject_class,
825       PROP_STUN_SERVER,
826       g_param_spec_string ("stun-server", "STUN Server",
827           "The STUN server of the form stun://hostname:port",
828           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
829
830   g_object_class_install_property (gobject_class,
831       PROP_TURN_SERVER,
832       g_param_spec_string ("turn-server", "TURN Server",
833           "The TURN server of the form turn(s)://username:password@host:port",
834           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
835
836   g_object_class_install_property (gobject_class,
837       PROP_CONTROLLER,
838       g_param_spec_boolean ("controller", "ICE controller",
839           "Whether the ICE agent is the controller or controlled. "
840           "In WebRTC, the initial offerrer is the ICE controller.", FALSE,
841           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
842
843   g_object_class_install_property (gobject_class,
844       PROP_AGENT,
845       g_param_spec_object ("agent", "ICE agent",
846           "ICE agent in use by this object", NICE_TYPE_AGENT,
847           G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
848
849   /**
850    * GstWebRTCICE::on-ice-candidate:
851    * @object: the #GstWebRtcBin
852    * @candidate: the ICE candidate
853    */
854   gst_webrtc_ice_signals[ON_ICE_CANDIDATE_SIGNAL] =
855       g_signal_new ("on-ice-candidate", G_TYPE_FROM_CLASS (klass),
856       G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_generic,
857       G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_STRING);
858 }
859
860 static void
861 gst_webrtc_ice_init (GstWebRTCICE * ice)
862 {
863   ice->priv =
864       G_TYPE_INSTANCE_GET_PRIVATE ((ice), GST_TYPE_WEBRTC_ICE,
865       GstWebRTCICEPrivate);
866
867   g_mutex_init (&ice->priv->lock);
868   g_cond_init (&ice->priv->cond);
869
870   _start_thread (ice);
871
872   ice->priv->nice_agent = nice_agent_new (ice->priv->main_context,
873       NICE_COMPATIBILITY_RFC5245);
874   g_signal_connect (ice->priv->nice_agent, "new-candidate-full",
875       G_CALLBACK (_on_new_candidate), ice);
876
877   ice->priv->nice_stream_map =
878       g_array_new (FALSE, TRUE, sizeof (struct NiceStreamItem));
879   g_array_set_clear_func (ice->priv->nice_stream_map,
880       (GDestroyNotify) _clear_ice_stream);
881 }
882
883 GstWebRTCICE *
884 gst_webrtc_ice_new (void)
885 {
886   return g_object_new (GST_TYPE_WEBRTC_ICE, NULL);
887 }