diff options
author | Youness Alaoui <youness.alaoui@collabora.co.uk> | 2013-02-01 19:22:08 -0500 |
---|---|---|
committer | Youness Alaoui <youness.alaoui@collabora.co.uk> | 2013-02-01 19:34:29 -0500 |
commit | bd3bb038275c29ebe8edabe82fabc20f39e6b04d (patch) | |
tree | fdc3ee1ae0a16a9a4ea908b94c65fba27eff5190 /examples | |
parent | 05b9f0af0102adc99f4fd914dab2b53d372b7aa4 (diff) | |
download | libnice-bd3bb038275c29ebe8edabe82fabc20f39e6b04d.tar.gz |
Add a simple example to showcase the API thanks to Bryce Allen
Diffstat (limited to 'examples')
-rw-r--r-- | examples/Makefile.am | 26 | ||||
-rw-r--r-- | examples/simple-example.c | 418 |
2 files changed, 444 insertions, 0 deletions
diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..ca1b21e --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,26 @@ +# +# Makefile.am for the Nice Glib ICE library +# +# (C) 2006, 2007 Collabora Ltd. +# (C) 2006, 2007 Nokia Corporation. All rights reserved. +# +# Licensed under MPL 1.1/LGPL 2.1. See file COPYING. + +include $(top_srcdir)/common.mk + +AM_CFLAGS = \ + -I $(top_srcdir) \ + -I $(top_srcdir)/agent \ + -I $(top_srcdir)/random \ + -I $(top_srcdir)/socket \ + -I $(top_srcdir)/stun \ + $(ERROR_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(GUPNP_CFLAGS) + +bin_PROGRAMS = simple-example + +simple_example_SOURCES = simple-example.c +simple_example_LDADD = $(top_builddir)/agent/libagent.la \ + $(top_builddir)/socket/libsocket.la \ + $(GLIB_LIBS) $(GUPNP_LIBS) diff --git a/examples/simple-example.c b/examples/simple-example.c new file mode 100644 index 0000000..b231d23 --- /dev/null +++ b/examples/simple-example.c @@ -0,0 +1,418 @@ +/* + * Copyright 2013 University of Chicago + * Contact: Bryce Allen + * Copyright 2013 Collabora Ltd. + * Contact: Youness Alaoui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Example using libnice to negotiate a UDP connection between two clients, + * possibly on the same network or behind different NATs and/or stateful + * firewalls. + * + * Build: + * gcc -o nicetest nicetest.c `pkg-config --cflags --libs nice` + * + * Run two clients, one controlling and one controlled: + * nicetest 0 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }') + * nicetest 1 $(host -4 -t A stun.stunprotocol.org | awk '{ print $4 }') + */ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +#include <agent.h> + +static GMainLoop *gloop; +static GIOChannel* io_stdin; +static guint stream_id; + +static const gchar *candidate_type_name[] = {"host", "srflx", "prflx", "relay"}; + +static const gchar *state_name[] = {"disconnected", "gathering", "connecting", + "connected", "ready", "failed"}; + +static int print_local_data(NiceAgent *agent, guint stream_id, + guint component_id); +static int parse_remote_data(NiceAgent *agent, guint stream_id, + guint component_id, char *line); +static void cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, + gpointer data); +static void cb_new_selected_pair(NiceAgent *agent, guint stream_id, + guint component_id, gchar *lfoundation, + gchar *rfoundation, gpointer data); +static void cb_component_state_changed(NiceAgent *agent, guint stream_id, + guint component_id, guint state, + gpointer data); +static void cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id, + guint len, gchar *buf, gpointer data); +static gboolean stdin_remote_info_cb (GIOChannel *source, GIOCondition cond, + gpointer data); +static gboolean stdin_send_data_cb (GIOChannel *source, GIOCondition cond, + gpointer data); + +int +main(int argc, char *argv[]) +{ + NiceAgent *agent; + gchar *stun_addr = NULL; + guint stun_port; + gboolean controlling; + + // Parse arguments + if (argc > 4 || argc < 2 || argv[1][1] != '\0') { + fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]); + return EXIT_FAILURE; + } + controlling = argv[1][0] - '0'; + if (controlling != 0 && controlling != 1) { + fprintf(stderr, "Usage: %s 0|1 stun_addr [stun_port]\n", argv[0]); + return EXIT_FAILURE; + } + + if (argc > 2) { + stun_addr = argv[2]; + if (argc > 3) + stun_port = atoi(argv[3]); + else + stun_port = 3478; + + g_debug("Using stun server '[%s]:%u'\n", stun_addr, stun_port); + } + + g_type_init(); + + gloop = g_main_loop_new(NULL, FALSE); + io_stdin = g_io_channel_unix_new(fileno(stdin)); + + // Create the nice agent + agent = nice_agent_new(g_main_loop_get_context (gloop), + NICE_COMPATIBILITY_RFC5245); + if (agent == NULL) + g_error("Failed to create agent"); + + // Set the STUN settings and controlling mode + if (stun_addr) { + g_object_set(G_OBJECT(agent), "stun-server", stun_addr, NULL); + g_object_set(G_OBJECT(agent), "stun-server-port", stun_port, NULL); + } + g_object_set(G_OBJECT(agent), "controlling-mode", controlling, NULL); + + // Connect to the signals + g_signal_connect(G_OBJECT(agent), "candidate-gathering-done", + G_CALLBACK(cb_candidate_gathering_done), NULL); + g_signal_connect(G_OBJECT(agent), "new-selected-pair", + G_CALLBACK(cb_new_selected_pair), NULL); + g_signal_connect(G_OBJECT(agent), "component-state-changed", + G_CALLBACK(cb_component_state_changed), NULL); + + // Create a new stream with one component + stream_id = nice_agent_add_stream(agent, 1); + if (stream_id == 0) + g_error("Failed to add stream"); + + // Attach to the component to receive the data + // Without this call, candidates cannot be gathered + nice_agent_attach_recv(agent, stream_id, 1, + g_main_loop_get_context (gloop), cb_nice_recv, NULL); + + // Start gathering local candidates + if (!nice_agent_gather_candidates(agent, stream_id)) + g_error("Failed to start candidate gathering"); + + g_debug("waiting for candidate-gathering-done signal..."); + + // Run the mainloop. Everything else will happen asynchronously + // when the candidates are done gathering. + g_main_loop_run (gloop); + + g_main_loop_unref(gloop); + g_object_unref(agent); + g_io_channel_unref (io_stdin); + + return EXIT_SUCCESS; +} + +static void +cb_candidate_gathering_done(NiceAgent *agent, guint stream_id, + gpointer data) +{ + + g_debug("SIGNAL candidate gathering done\n"); + + // Candidate gathering is done. Send our local candidates on stdout + printf("Copy this line to remote client:\n"); + printf("\n "); + print_local_data(agent, stream_id, 1); + printf("\n"); + + // Listen on stdin for the remote candidate list + printf("Enter remote data (single line, no wrapping):\n"); + g_io_add_watch(io_stdin, G_IO_IN, stdin_remote_info_cb, agent); + printf("> "); + fflush (stdout); +} + +static gboolean +stdin_remote_info_cb (GIOChannel *source, GIOCondition cond, + gpointer data) +{ + NiceAgent *agent = data; + gchar *line = NULL; + int rval; + gboolean ret = TRUE; + + if (g_io_channel_read_line (source, &line, NULL, NULL, NULL) == + G_IO_STATUS_NORMAL) { + + // Parse remote candidate list and set it on the agent + rval = parse_remote_data(agent, stream_id, 1, line); + if (rval == EXIT_SUCCESS) { + // Return FALSE so we stop listening to stdin since we parsed the + // candidates correctly + ret = FALSE; + g_debug("waiting for state READY or FAILED signal..."); + } else { + fprintf(stderr, "ERROR: failed to parse remote data\n"); + printf("Enter remote data (single line, no wrapping):\n"); + printf("> "); + fflush (stdout); + } + g_free (line); + } + + return ret; +} + +static void +cb_component_state_changed(NiceAgent *agent, guint stream_id, + guint component_id, guint state, + gpointer data) +{ + + g_debug("SIGNAL: state changed %d %d %s[%d]\n", + stream_id, component_id, state_name[state], state); + + if (state == NICE_COMPONENT_STATE_READY) { + NiceCandidate *local, *remote; + + // Get current selected candidate pair and print IP address used + if (nice_agent_get_selected_pair (agent, stream_id, component_id, + &local, &remote)) { + gchar ipaddr[INET6_ADDRSTRLEN]; + + nice_address_to_string(&local->addr, ipaddr); + printf("\nNegotiation complete: ([%s]:%d,", + ipaddr, nice_address_get_port(&local->addr)); + nice_address_to_string(&remote->addr, ipaddr); + printf(" [%s]:%d)\n", ipaddr, nice_address_get_port(&remote->addr)); + } + + // Listen to stdin and send data written to it + printf("\nSend lines to remote (Ctrl-D to quit):\n"); + g_io_add_watch(io_stdin, G_IO_IN, stdin_send_data_cb, agent); + printf("> "); + fflush (stdout); + } else if (state == NICE_COMPONENT_STATE_FAILED) { + g_main_loop_quit (gloop); + } +} + +static gboolean +stdin_send_data_cb (GIOChannel *source, GIOCondition cond, + gpointer data) +{ + NiceAgent *agent = data; + gchar *line = NULL; + + if (g_io_channel_read_line (source, &line, NULL, NULL, NULL) == + G_IO_STATUS_NORMAL) { + nice_agent_send(agent, stream_id, 1, strlen(line), line); + g_free (line); + printf("> "); + fflush (stdout); + } + + return TRUE; +} + +static void +cb_new_selected_pair(NiceAgent *agent, guint stream_id, + guint component_id, gchar *lfoundation, + gchar *rfoundation, gpointer data) +{ + g_debug("SIGNAL: selected pair %s %s", lfoundation, rfoundation); +} + +static void +cb_nice_recv(NiceAgent *agent, guint stream_id, guint component_id, + guint len, gchar *buf, gpointer data) +{ + printf("%.*s", len, buf); + fflush(stdout); +} + +static NiceCandidate * +parse_candidate(char *scand, guint stream_id) +{ + NiceCandidate *cand = NULL; + NiceCandidateType ntype; + gchar **tokens = NULL; + guint i; + + tokens = g_strsplit (scand, ",", 5); + for (i = 0; tokens && tokens[i]; i++); + if (i != 5) + goto end; + + for (i = 0; i < G_N_ELEMENTS (candidate_type_name); i++) { + if (strcmp(tokens[4], candidate_type_name[i]) == 0) { + ntype = i; + break; + } + } + if (i == G_N_ELEMENTS (candidate_type_name)) + goto end; + + cand = nice_candidate_new(ntype); + cand->component_id = 1; + cand->stream_id = stream_id; + cand->transport = NICE_CANDIDATE_TRANSPORT_UDP; + strncpy(cand->foundation, tokens[0], NICE_CANDIDATE_MAX_FOUNDATION); + cand->priority = atoi (tokens[1]); + + if (!nice_address_set_from_string(&cand->addr, tokens[2])) { + g_message("failed to parse addr: %s", tokens[2]); + nice_candidate_free(cand); + cand = NULL; + goto end; + } + + nice_address_set_port(&cand->addr, atoi (tokens[3])); + + end: + g_strfreev(tokens); + + return cand; +} + + +static int +print_local_data (NiceAgent *agent, guint stream_id, guint component_id) +{ + int result = EXIT_FAILURE; + gchar *local_ufrag = NULL; + gchar *local_password = NULL; + gchar ipaddr[INET6_ADDRSTRLEN]; + GSList *cands = NULL, *item; + + if (!nice_agent_get_local_credentials(agent, stream_id, + &local_ufrag, &local_password)) + goto end; + + cands = nice_agent_get_local_candidates(agent, stream_id, component_id); + if (cands == NULL) + goto end; + + printf("%s %s", local_ufrag, local_password); + + for (item = cands; item; item = item->next) { + NiceCandidate *c = (NiceCandidate *)item->data; + + nice_address_to_string(&c->addr, ipaddr); + + // (foundation),(prio),(addr),(port),(type) + printf(" %s,%u,%s,%u,%s", + c->foundation, + c->priority, + ipaddr, + nice_address_get_port(&c->addr), + candidate_type_name[c->type]); + } + printf("\n"); + result = EXIT_SUCCESS; + + end: + if (local_ufrag) + g_free(local_ufrag); + if (local_password) + g_free(local_password); + if (cands) + g_slist_free_full(cands, (GDestroyNotify)&nice_candidate_free); + + return result; +} + + +static int +parse_remote_data(NiceAgent *agent, guint stream_id, + guint component_id, char *line) +{ + GSList *remote_candidates = NULL; + gchar **line_argv = NULL; + const gchar *ufrag = NULL; + const gchar *passwd = NULL; + int result = EXIT_FAILURE; + int i; + + line_argv = g_strsplit_set (line, " \t\n", 0); + for (i = 0; line_argv && line_argv[i]; i++) { + if (strlen (line_argv[i]) == 0) + continue; + + // first two args are remote ufrag and password + if (!ufrag) { + ufrag = line_argv[i]; + } else if (!passwd) { + passwd = line_argv[i]; + } else { + // Remaining args are serialized canidates (at least one is required) + NiceCandidate *c = parse_candidate(line_argv[i], stream_id); + + if (c == NULL) { + g_message("failed to parse candidate: %s", line_argv[i]); + goto end; + } + remote_candidates = g_slist_prepend(remote_candidates, c); + } + } + if (ufrag == NULL || passwd == NULL || remote_candidates == NULL) { + g_message("line must have at least ufrag, password, and one candidate"); + goto end; + } + + if (!nice_agent_set_remote_credentials(agent, stream_id, ufrag, passwd)) { + g_message("failed to set remote credentials"); + goto end; + } + + // Note: this will trigger the start of negotiation. + if (nice_agent_set_remote_candidates(agent, stream_id, component_id, + remote_candidates) < 1) { + g_message("failed to set remote candidates"); + goto end; + } + + result = EXIT_SUCCESS; + + end: + if (line_argv != NULL) + g_strfreev(line_argv); + if (remote_candidates != NULL) + g_slist_free_full(remote_candidates, (GDestroyNotify)&nice_candidate_free); + + return result; +} |