Imported Upstream version 0.1.17
[platform/upstream/libnice.git] / examples / threaded-example.c
1 /*
2  * Copyright 2013 University of Chicago
3  *  Contact: Bryce Allen
4  * Copyright 2013 Collabora Ltd.
5  *  Contact: Youness Alaoui
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * Alternatively, the contents of this file may be used under the terms of the
18  * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which
19  * case the provisions of LGPL are applicable instead of those above. If you
20  * wish to allow use of your version of this file only under the terms of the
21  * LGPL and not to allow others to use your version of this file under the
22  * MPL, indicate your decision by deleting the provisions above and replace
23  * them with the notice and other provisions required by the LGPL. If you do
24  * not delete the provisions above, a recipient may use your version of this
25  * file under either the MPL or the LGPL.
26  */
27
28 /*
29  * Example using libnice to negotiate a UDP connection between two clients,
30  * possibly on the same network or behind different NATs and/or stateful
31  * firewalls.
32  *
33  * Build:
34  *   gcc -o threaded-example threaded-example.c `pkg-config --cflags --libs nice`
35  *
36  * Run two clients, one controlling and one controlled:
37  *   threaded-example 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
38  *   threaded-example 1 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }')
39  */
40 #include <stdlib.h>
41 #include <stdio.h>
42 #include <string.h>
43 #include <ctype.h>
44
45 #include <agent.h>
46
47 #include <gio/gnetworking.h>
48
49 static GMainLoop *gloop;
50 static gchar *stun_addr = NULL;
51 static guint stun_port;
52 static gboolean controlling;
53 static gboolean exit_thread, candidate_gathering_done, negotiation_done;
54 static GMutex gather_mutex, negotiate_mutex;
55 static GCond gather_cond, negotiate_cond;
56
57 static const gchar *candidate_type_name[] = {"host", "srflx", "prflx", "relay"};
58
59 static const gchar *state_name[] = {"disconnected", "gathering", "connecting",
60                                     "connected", "ready", "failed"};
61
62 static int print_local_data(NiceAgent *agent, guint stream_id,
63     guint component_id);
64 static int parse_remote_data(NiceAgent *agent, guint stream_id,
65     guint component_id, char *line);
66 static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
67     gpointer data);
68 static void cb_new_selected_pair(NiceAgent *agent, guint stream_id,
69     guint component_id, gchar *lfoundation,
70     gchar *rfoundation, gpointer data);
71 static void cb_component_state_changed(NiceAgent *agent, guint stream_id,
72     guint component_id, guint state,
73     gpointer data);
74 static void cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
75     guint len, gchar *buf, gpointer data);
76
77 static void * example_thread(void *data);
78
79 int
80 main(int argc, char *argv[])
81 {
82   GThread *gexamplethread;
83
84   // Parse arguments
85   if (argc > 4 || argc < 2 || argv[1][1] != '\0') {
86     fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
87     return EXIT_FAILURE;
88   }
89   controlling = argv[1][0] - '0';
90   if (controlling != 0 && controlling != 1) {
91     fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]);
92     return EXIT_FAILURE;
93   }
94
95   if (argc > 2) {
96     stun_addr = argv[2];
97     if (argc > 3)
98       stun_port = atoi(argv[3]);
99     else
100       stun_port = 3478;
101
102     g_debug("Using stun server '[%s]:%u'\n", stun_addr, stun_port);
103   }
104
105   g_networking_init();
106
107   gloop = g_main_loop_new(NULL, FALSE);
108
109   // Run the mainloop and the example thread
110   exit_thread = FALSE;
111   gexamplethread = g_thread_new("example thread", &example_thread, NULL);
112   g_main_loop_run (gloop);
113   exit_thread = TRUE;
114
115   g_thread_join (gexamplethread);
116   g_main_loop_unref(gloop);
117
118   return EXIT_SUCCESS;
119 }
120
121 static void *
122 example_thread(void *data)
123 {
124   NiceAgent *agent;
125   NiceCandidate *local, *remote;
126   GIOChannel* io_stdin;
127   guint stream_id;
128   gchar *line = NULL;
129   int rval;
130
131 #ifdef G_OS_WIN32
132   io_stdin = g_io_channel_win32_new_fd(_fileno(stdin));
133 #else
134   io_stdin = g_io_channel_unix_new(fileno(stdin));
135 #endif
136   g_io_channel_set_flags (io_stdin, G_IO_FLAG_NONBLOCK, NULL);
137
138   // Create the nice agent
139   agent = nice_agent_new(g_main_loop_get_context (gloop),
140       NICE_COMPATIBILITY_RFC5245);
141   if (agent == NULL)
142     g_error("Failed to create agent");
143
144   // Set the STUN settings and controlling mode
145   if (stun_addr) {
146     g_object_set(agent, "stun-server", stun_addr, NULL);
147     g_object_set(agent, "stun-server-port", stun_port, NULL);
148   }
149   g_object_set(agent, "controlling-mode", controlling, NULL);
150
151   // Connect to the signals
152   g_signal_connect(agent, "candidate-gathering-done",
153       G_CALLBACK(cb_candidate_gathering_done), NULL);
154   g_signal_connect(agent, "new-selected-pair",
155       G_CALLBACK(cb_new_selected_pair), NULL);
156   g_signal_connect(agent, "component-state-changed",
157       G_CALLBACK(cb_component_state_changed), NULL);
158
159   // Create a new stream with one component
160   stream_id = nice_agent_add_stream(agent, 1);
161   if (stream_id == 0)
162     g_error("Failed to add stream");
163
164   // Attach to the component to receive the data
165   // Without this call, candidates cannot be gathered
166   nice_agent_attach_recv(agent, stream_id, 1,
167       g_main_loop_get_context (gloop), cb_nice_recv, NULL);
168
169   // Start gathering local candidates
170   if (!nice_agent_gather_candidates(agent, stream_id))
171     g_error("Failed to start candidate gathering");
172
173   g_debug("waiting for candidate-gathering-done signal...");
174
175   g_mutex_lock(&gather_mutex);
176   while (!exit_thread && !candidate_gathering_done)
177     g_cond_wait(&gather_cond, &gather_mutex);
178   g_mutex_unlock(&gather_mutex);
179   if (exit_thread)
180     goto end;
181
182   // Candidate gathering is done. Send our local candidates on stdout
183   printf("Copy this line to remote client:\n");
184   printf("\n  ");
185   print_local_data(agent, stream_id, 1);
186   printf("\n");
187
188   // Listen on stdin for the remote candidate list
189   printf("Enter remote data (single line, no wrapping):\n");
190   printf("> ");
191   fflush (stdout);
192   while (!exit_thread) {
193     GIOStatus s = g_io_channel_read_line (io_stdin, &line, NULL, NULL, NULL);
194     if (s == G_IO_STATUS_NORMAL) {
195       // Parse remote candidate list and set it on the agent
196       rval = parse_remote_data(agent, stream_id, 1, line);
197       if (rval == EXIT_SUCCESS) {
198         g_free (line);
199         break;
200       } else {
201         fprintf(stderr, "ERROR: failed to parse remote data\n");
202         printf("Enter remote data (single line, no wrapping):\n");
203         printf("> ");
204         fflush (stdout);
205       }
206       g_free (line);
207     } else if (s == G_IO_STATUS_AGAIN) {
208       g_usleep (100000);
209     }
210   }
211
212   g_debug("waiting for state READY or FAILED signal...");
213   g_mutex_lock(&negotiate_mutex);
214   while (!exit_thread && !negotiation_done)
215     g_cond_wait(&negotiate_cond, &negotiate_mutex);
216   g_mutex_unlock(&negotiate_mutex);
217   if (exit_thread)
218     goto end;
219
220   // Get current selected candidate pair and print IP address used
221   if (nice_agent_get_selected_pair (agent, stream_id, 1,
222           &local, &remote)) {
223     gchar ipaddr[INET6_ADDRSTRLEN];
224
225     nice_address_to_string(&local->addr, ipaddr);
226     printf("\nNegotiation complete: ([%s]:%d,",
227         ipaddr, nice_address_get_port(&local->addr));
228     nice_address_to_string(&remote->addr, ipaddr);
229     printf(" [%s]:%d)\n", ipaddr, nice_address_get_port(&remote->addr));
230   }
231
232   // Listen to stdin and send data written to it
233   printf("\nSend lines to remote (Ctrl-D to quit):\n");
234   printf("> ");
235   fflush (stdout);
236   while (!exit_thread) {
237     GIOStatus s = g_io_channel_read_line (io_stdin, &line, NULL, NULL, NULL);
238     if (s == G_IO_STATUS_NORMAL) {
239       nice_agent_send(agent, stream_id, 1, strlen(line), line);
240       g_free (line);
241       printf("> ");
242       fflush (stdout);
243     } else if (s == G_IO_STATUS_AGAIN) {
244       g_usleep (100000);
245     } else {
246       // Ctrl-D was pressed.
247       nice_agent_send(agent, stream_id, 1, 1, "\0");
248       break;
249     }
250   }
251
252 end:
253   g_io_channel_unref (io_stdin);
254   g_object_unref(agent);
255   g_main_loop_quit (gloop);
256
257   return NULL;
258 }
259
260 static void
261 cb_candidate_gathering_done(NiceAgent *agent, guint stream_id,
262     gpointer data)
263 {
264   g_debug("SIGNAL candidate gathering done\n");
265
266   g_mutex_lock(&gather_mutex);
267   candidate_gathering_done = TRUE;
268   g_cond_signal(&gather_cond);
269   g_mutex_unlock(&gather_mutex);
270 }
271
272 static void
273 cb_component_state_changed(NiceAgent *agent, guint stream_id,
274     guint component_id, guint state,
275     gpointer data)
276 {
277   g_debug("SIGNAL: state changed %d %d %s[%d]\n",
278       stream_id, component_id, state_name[state], state);
279
280   if (state == NICE_COMPONENT_STATE_READY) {
281     g_mutex_lock(&negotiate_mutex);
282     negotiation_done = TRUE;
283     g_cond_signal(&negotiate_cond);
284     g_mutex_unlock(&negotiate_mutex);
285   } else if (state == NICE_COMPONENT_STATE_FAILED) {
286     g_main_loop_quit (gloop);
287   }
288 }
289
290
291 static void
292 cb_new_selected_pair(NiceAgent *agent, guint stream_id,
293     guint component_id, gchar *lfoundation,
294     gchar *rfoundation, gpointer data)
295 {
296   g_debug("SIGNAL: selected pair %s %s", lfoundation, rfoundation);
297 }
298
299 static void
300 cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id,
301     guint len, gchar *buf, gpointer data)
302 {
303   if (len == 1 && buf[0] == '\0')
304     g_main_loop_quit (gloop);
305
306   printf("%.*s", len, buf);
307   fflush(stdout);
308 }
309
310 static NiceCandidate *
311 parse_candidate(char *scand, guint stream_id)
312 {
313   NiceCandidate *cand = NULL;
314   NiceCandidateType ntype = NICE_CANDIDATE_TYPE_HOST;
315   gchar **tokens = NULL;
316   guint i;
317
318   tokens = g_strsplit (scand, ",", 5);
319   for (i = 0; tokens[i]; i++);
320   if (i != 5)
321     goto end;
322
323   for (i = 0; i < G_N_ELEMENTS (candidate_type_name); i++) {
324     if (strcmp(tokens[4], candidate_type_name[i]) == 0) {
325       ntype = i;
326       break;
327     }
328   }
329   if (i == G_N_ELEMENTS (candidate_type_name))
330     goto end;
331
332   cand = nice_candidate_new(ntype);
333   cand->component_id = 1;
334   cand->stream_id = stream_id;
335   cand->transport = NICE_CANDIDATE_TRANSPORT_UDP;
336   strncpy(cand->foundation, tokens[0], NICE_CANDIDATE_MAX_FOUNDATION - 1);
337   cand->foundation[NICE_CANDIDATE_MAX_FOUNDATION - 1] = 0;
338   cand->priority = atoi (tokens[1]);
339
340   if (!nice_address_set_from_string(&cand->addr, tokens[2])) {
341     g_message("failed to parse addr: %s", tokens[2]);
342     nice_candidate_free(cand);
343     cand = NULL;
344     goto end;
345   }
346
347   nice_address_set_port(&cand->addr, atoi (tokens[3]));
348
349  end:
350   g_strfreev(tokens);
351
352   return cand;
353 }
354
355
356 static int
357 print_local_data (NiceAgent *agent, guint stream_id, guint component_id)
358 {
359   int result = EXIT_FAILURE;
360   gchar *local_ufrag = NULL;
361   gchar *local_password = NULL;
362   gchar ipaddr[INET6_ADDRSTRLEN];
363   GSList *cands = NULL, *item;
364
365   if (!nice_agent_get_local_credentials(agent, stream_id,
366       &local_ufrag, &local_password))
367     goto end;
368
369   cands = nice_agent_get_local_candidates(agent, stream_id, component_id);
370   if (cands == NULL)
371     goto end;
372
373   printf("%s %s", local_ufrag, local_password);
374
375   for (item = cands; item; item = item->next) {
376     NiceCandidate *c = (NiceCandidate *)item->data;
377
378     nice_address_to_string(&c->addr, ipaddr);
379
380     // (foundation),(prio),(addr),(port),(type)
381     printf(" %s,%u,%s,%u,%s",
382         c->foundation,
383         c->priority,
384         ipaddr,
385         nice_address_get_port(&c->addr),
386         candidate_type_name[c->type]);
387   }
388   printf("\n");
389   result = EXIT_SUCCESS;
390
391  end:
392   if (local_ufrag)
393     g_free(local_ufrag);
394   if (local_password)
395     g_free(local_password);
396   if (cands)
397     g_slist_free_full(cands, (GDestroyNotify)&nice_candidate_free);
398
399   return result;
400 }
401
402
403 static int
404 parse_remote_data(NiceAgent *agent, guint stream_id,
405     guint component_id, char *line)
406 {
407   GSList *remote_candidates = NULL;
408   gchar **line_argv = NULL;
409   const gchar *ufrag = NULL;
410   const gchar *passwd = NULL;
411   int result = EXIT_FAILURE;
412   int i;
413
414   line_argv = g_strsplit_set (line, " \t\n", 0);
415   for (i = 0; line_argv && line_argv[i]; i++) {
416     if (strlen (line_argv[i]) == 0)
417       continue;
418
419     // first two args are remote ufrag and password
420     if (!ufrag) {
421       ufrag = line_argv[i];
422     } else if (!passwd) {
423       passwd = line_argv[i];
424     } else {
425       // Remaining args are serialized canidates (at least one is required)
426       NiceCandidate *c = parse_candidate(line_argv[i], stream_id);
427
428       if (c == NULL) {
429         g_message("failed to parse candidate: %s", line_argv[i]);
430         goto end;
431       }
432       remote_candidates = g_slist_prepend(remote_candidates, c);
433     }
434   }
435   if (ufrag == NULL || passwd == NULL || remote_candidates == NULL) {
436     g_message("line must have at least ufrag, password, and one candidate");
437     goto end;
438   }
439
440   if (!nice_agent_set_remote_credentials(agent, stream_id, ufrag, passwd)) {
441     g_message("failed to set remote credentials");
442     goto end;
443   }
444
445   // Note: this will trigger the start of negotiation.
446   if (nice_agent_set_remote_candidates(agent, stream_id, component_id,
447       remote_candidates) < 1) {
448     g_message("failed to set remote candidates");
449     goto end;
450   }
451
452   result = EXIT_SUCCESS;
453
454  end:
455   if (line_argv != NULL)
456     g_strfreev(line_argv);
457   if (remote_candidates != NULL)
458     g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free);
459
460   return result;
461 }