From 729a0fbeb44f8ea44f9406c6c07a8dcb1eb3b776 Mon Sep 17 00:00:00 2001 From: Philip Withnall Date: Mon, 22 Sep 2014 15:16:06 +0100 Subject: [PATCH] agent: Add an asychronous version of nice_agent_gather_candidates() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This wraps the entire candidate gathering operation, from local candidates through to an established TCP (or UDP) connection, or failure. It is not ready to merge, requiring: • Thought about the API design. • Unit tests. • Documentation. • Introspection. --- agent/Makefile.am | 3 + agent/async.c | 626 ++++++++++++++++++++++++++++ agent/async.h | 64 +++ docs/reference/libnice/Makefile.am | 1 + docs/reference/libnice/libnice-sections.txt | 4 + nice/libnice.sym | 4 + nice/nice.h | 1 + win32/vs9/libnice.def | 4 + win32/vs9/libnice.vcproj | 8 + 9 files changed, 715 insertions(+) create mode 100644 agent/async.c create mode 100644 agent/async.h diff --git a/agent/Makefile.am b/agent/Makefile.am index b585393..eafb908 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -27,6 +27,8 @@ noinst_LTLIBRARIES = libagent.la libagent_la_SOURCES = \ address.h \ address.c \ + async.h \ + async.c \ debug.h \ debug.c \ candidate.h \ @@ -68,6 +70,7 @@ libagent_la_DEPENDENCIES = \ pkginclude_HEADERS = \ agent.h \ + async.h \ candidate.h \ debug.h \ address.h \ diff --git a/agent/async.c b/agent/async.c new file mode 100644 index 0000000..0d3735e --- /dev/null +++ b/agent/async.c @@ -0,0 +1,626 @@ +/* + * This file is part of the Nice GLib ICE library. + * + * (C) 2014 Collabora Ltd. + * Contact: Philip Withnall + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Nice GLib ICE library. + * + * The Initial Developers of the Original Code are Collabora Ltd and Nokia + * Corporation. All Rights Reserved. + * + * Contributors: + * Philip Withnall, Collabora Ltd. + * + * Alternatively, the contents of this file may be used under the terms of the + * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which + * case the provisions of LGPL are applicable instead of those above. If you + * wish to allow use of your version of this file only under the terms of the + * LGPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the LGPL. + */ + +#include + +#include +#include + +#include "agent.h" +#include "async.h" + +/** + * SECTION:async + * @short_description: Asynchronous ICE helper functions + * @title: NiceAgent + * @stability: Unstable + * @include: agent/async.h + * + * Asynchronous wrapper around nice_agent_gather_candidates(). + * + * FIXME: This is unfinished and not yet mergeable. + * TODO: Eliminate idle handlers. + * + * Since: UNRELEASED + */ + +typedef struct { + /* Static state. */ + NiceAgent *agent; /* owned */ + guint stream_id; + guint component_id; + GSocketProtocol protocol; + GCancellable *cancellable; /* unowned */ + gboolean task_returned; + + gulong component_state_changed_handler; + gulong reliable_transport_writable_handler; + gulong cancelled_handler; + + /* Operational state. */ + gboolean got_final_turn_server; + gboolean got_final_candidate; + NiceComponentState component_state; +} IceGatheringData; + +static void +ice_gathering_data_disconnect_signals (IceGatheringData *data) +{ + if (data->agent != NULL && data->component_state_changed_handler != 0) { + g_signal_handler_disconnect (data->agent, + data->component_state_changed_handler); + data->component_state_changed_handler = 0; + } + + if (data->agent != NULL && + data->reliable_transport_writable_handler != 0) { + g_signal_handler_disconnect (data->agent, + data->reliable_transport_writable_handler); + data->reliable_transport_writable_handler = 0; + } + + if (data->cancellable != NULL && data->cancelled_handler != 0) { + g_signal_handler_disconnect (data->cancellable, + data->cancelled_handler); + data->cancelled_handler = 0; + } +} + +static void +ice_gathering_data_free (IceGatheringData *data) +{ + ice_gathering_data_disconnect_signals (data); + g_clear_object (&data->agent); + + g_slice_free (IceGatheringData, data); +} + +static gboolean +check_for_failure (GTask *task); +static void +complete_task (GTask *task, GError *error); + +typedef struct { + NiceAgent *agent; /* owned */ + guint stream_id; + guint component_id; + NiceComponentState state; + GTask *task; /* owned */ +} ComponentStateChangedData; + +static void +component_state_changed_data_free (ComponentStateChangedData *data) +{ + g_object_unref (data->agent); + g_object_unref (data->task); + + g_slice_free (ComponentStateChangedData, data); +} + +/* This needs to be called in the #GTask’s thread so that any messages it sends + * have their callbacks emitted in the right thread. */ +static gboolean +component_state_changed_idle_cb (gpointer user_data) +{ + ComponentStateChangedData *state_data; /* unowned */ + GTask *task; /* unowned */ + IceGatheringData *ice_data; /* unowned */ + NiceComponentState state; + + state_data = user_data; + task = state_data->task; + ice_data = g_task_get_task_data (task); + state = state_data->state; + + g_assert (ice_data->agent == state_data->agent); + g_assert (g_main_context_is_owner (g_task_get_context (task))); + + /* Right component? */ + if (state_data->stream_id != ice_data->stream_id || + state_data->component_id != ice_data->component_id) { + return G_SOURCE_REMOVE; + } + + g_debug ("Changing state: %s → %s.", + nice_component_state_to_string (ice_data->component_state), + nice_component_state_to_string (state)); + + ice_data->component_state = state; + + /* Has the handler been removed while we were in the idle queue? */ + if (ice_data->component_state_changed_handler == 0) { + return G_SOURCE_REMOVE; + } + + /* Failed? */ + if (check_for_failure (task)) { + return G_SOURCE_REMOVE; + } + + /* UDP succeeded? */ + if (ice_data->protocol == G_SOCKET_PROTOCOL_UDP) { + /* The #GTask could be completed in + * %NICE_COMPONENT_STATE_CONNECTED instead of + * %NICE_COMPONENT_STATE_READY for faster connection + * establishment at the price of possibly having the route + * change afterwards. */ + if (state == NICE_COMPONENT_STATE_CONNECTED || + state == NICE_COMPONENT_STATE_READY) { + complete_task (task, NULL); + } + } + + return G_SOURCE_REMOVE; +} + +/* This could be emitted in any thread, e.g. the glib-networking TLS handshake + * thread. */ +static void +component_state_changed_cb (NiceAgent *agent, guint stream_id, + guint component_id, guint state, gpointer user_data) +{ + GTask *task; /* unowned */ + ComponentStateChangedData *data = NULL; /* owned */ + + task = G_TASK (user_data); + g_assert (g_task_is_valid (task, agent)); + + data = g_slice_new (ComponentStateChangedData); + + data->agent = g_object_ref (agent); + data->stream_id = stream_id; + data->component_id = component_id; + data->state = state; + data->task = g_object_ref (task); + + /* Shunt over to the #GTask’s context, or any messages we send may get + * callbacks in the wrong thread. */ + g_main_context_invoke_full (g_task_get_context (task), + G_PRIORITY_DEFAULT, + component_state_changed_idle_cb, + data, + (GDestroyNotify) component_state_changed_data_free); +} + +typedef struct { + NiceAgent *agent; /* owned */ + guint stream_id; + guint component_id; + GTask *task; /* owned */ +} ReliableTransportWritableData; + +static void +reliable_transport_writable_data_free (ReliableTransportWritableData *data) +{ + g_object_unref (data->agent); + g_object_unref (data->task); + + g_slice_free (ReliableTransportWritableData, data); +} + +/* The TCP connection is complete and data can now be sent on it. */ +static gboolean +reliable_transport_writable_idle_cb (gpointer user_data) +{ + ReliableTransportWritableData *reliable_data; /* unowned */ + GTask *task; /* unowned */ + IceGatheringData *ice_data; /* unowned */ + + reliable_data = user_data; + task = reliable_data->task; + ice_data = g_task_get_task_data (task); + + g_assert (ice_data->agent == reliable_data->agent); + g_assert (g_main_context_is_owner (g_task_get_context (task))); + + /* Right component? */ + if (reliable_data->stream_id != ice_data->stream_id || + reliable_data->component_id != ice_data->component_id) { + return G_SOURCE_REMOVE; + } + + g_debug ("Reliable transport is now writeable."); + + /* Has the handler been removed while we were in the idle queue? */ + if (ice_data->reliable_transport_writable_handler == 0) { + return G_SOURCE_REMOVE; + } + + /* Success! The TCP stream is now writeable. */ + g_warn_if_fail (ice_data->protocol == G_SOCKET_PROTOCOL_TCP); + + complete_task (task, NULL); + + return G_SOURCE_REMOVE; +} + +/* This could be emitted in any thread, e.g. the glib-networking TLS handshake + * thread. */ +static void +reliable_transport_writable_cb (NiceAgent *agent, guint stream_id, + guint component_id, gpointer user_data) +{ + GTask *task; /* unowned */ + ReliableTransportWritableData *data = NULL; /* owned */ + + task = G_TASK (user_data); + g_assert (g_task_is_valid (task, agent)); + + data = g_slice_new (ReliableTransportWritableData); + + data->agent = g_object_ref (agent); + data->stream_id = stream_id; + data->component_id = component_id; + data->task = g_object_ref (task); + + /* Shunt over to the #GTask’s context, or any messages we send may get + * callbacks in the wrong thread. */ + g_main_context_invoke_full (g_task_get_context (task), + G_PRIORITY_DEFAULT, + reliable_transport_writable_idle_cb, + data, + (GDestroyNotify) reliable_transport_writable_data_free); +} + +static gboolean +cancelled_idle_cb (gpointer user_data) +{ + GTask *task; /* unowned */ + GCancellable *cancellable; /* unowned */ + IceGatheringData *data; /* unowned */ + GError *child_error = NULL; + + task = user_data; + data = g_task_get_task_data (task); + cancellable = g_task_get_cancellable (task); + + g_assert (g_main_context_is_owner (g_task_get_context (task))); + + g_debug ("ICE negotiation cancelled."); + + /* Has the handler been removed while we were in the idle queue? */ + if (data->cancelled_handler == 0) { + return G_SOURCE_REMOVE; + } + + /* The ICE negotiation has been cancelled. */ + if (g_cancellable_set_error_if_cancelled (cancellable, &child_error)) { + complete_task (task, child_error); + } + + return G_SOURCE_REMOVE; +} + +/* This could be emitted in any thread, e.g. the glib-networking TLS handshake + * thread. */ +static void +cancelled_cb (GCancellable *cancellable, gpointer user_data) +{ + GTask *task; /* unowned */ + + task = G_TASK (user_data); + + /* Shunt over to the #GTask’s context, or any messages we send may get + * callbacks in the wrong thread. */ + g_main_context_invoke_full (g_task_get_context (task), + G_PRIORITY_DEFAULT, + cancelled_idle_cb, g_object_ref (task), + (GDestroyNotify) g_object_unref); +} + +/** + * nice_agent_gather_candidates_async: + * @agent: a #NiceAgent + * @stream_id: ID of the ICE stream to use + * @component_id: ID of the ICE component to use + * @protocol: protocol the connection is being negotiated for + * @cancellable: (allow-none): a #GCancellable, or %NULL + * @callback: (scope async): completion callback + * @user_data: (closure callback): data for @callback + * + * Asynchronously run an ICE candidate gathering and negotiation operation on + * the given @stream_id in @agent. This gathers local candidates, gathers remote + * candidates (the caller is responsible for this), pairs them and tests each + * pair for connectivity. The asynchronous operation completes when negotiation + * succeeds and a candidate pair is selected for communication between this + * host and its peer; or when negotiation fails. + * + * Before the operation is guaranteed to complete, + * nice_agent_gather_candidates_got_final_candidate() and + * nice_agent_gather_candidates_got_final_turn_server() must both be called, + * after the final remote candidate and TURN server (respectively) have been set + * on the #NiceAgent. If TURN servers are not being used, + * nice_agent_gather_candidates_got_final_turn_server() can be called without + * having set any TURN servers on the agent. The operation may complete before + * these functions have been called if, for example, a reliable connection can + * be established without TURN servers. + * + * The caller must have previously attached the #NiceAgent to a main context + * somehow, either via attaching a #NiceIOStream, or by calling + * nice_agent_attach_recv(). They must also have set the remote credentials + * using nice_agent_set_remote_credentials(). + * + * The caller must also attach to #NiceAgent::new-candidate and exchange + * candidates out of band, adding remote candidates using + * nice_agent_set_remote_candidates() and TURN servers (if desired) using + * nice_agent_set_relay_info(). + * + * Possible error values are: %G_IO_ERROR_INVALID_ARGUMENT if the @stream_id is + * invalid, or if an invalid local address has previously been set on the + * @agent; %G_IO_ERROR_CANCELLED if the operation is cancelled; or + * %G_IO_ERROR_HOST_UNREACHABLE if ICE negotiation fails. + * + * Returns: (transfer full): candidate gathering task; unref with + * g_object_unref() + * + * Since: UNRELEASED + */ +GTask * +nice_agent_gather_candidates_async (NiceAgent *agent, guint stream_id, + guint component_id, + GSocketProtocol protocol, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GTask *task = NULL; /* owned */ + IceGatheringData *data = NULL; /* owned */ + gboolean ret; + GError *child_error = NULL; + + g_return_val_if_fail (NICE_IS_AGENT (agent), NULL); + g_return_val_if_fail (stream_id > 0, NULL); + g_return_val_if_fail (component_id > 0, NULL); + g_return_val_if_fail (protocol == G_SOCKET_PROTOCOL_TCP || + protocol == G_SOCKET_PROTOCOL_UDP, NULL); + g_return_val_if_fail (cancellable == NULL || + G_IS_CANCELLABLE (cancellable), NULL); + + /* TODO: What if the component is already READY? */ + + /* Create the task structure. */ + task = g_task_new (agent, cancellable, callback, user_data); + g_task_set_source_tag (task, nice_agent_gather_candidates_async); + + data = g_slice_new0 (IceGatheringData); + data->agent = g_object_ref (agent); + data->stream_id = stream_id; + data->component_id = component_id; + data->protocol = protocol; + data->cancellable = cancellable; + data->task_returned = FALSE; + data->component_state_changed_handler = 0; + data->reliable_transport_writable_handler = 0; + data->cancelled_handler = 0; + data->got_final_turn_server = FALSE; + data->got_final_candidate = FALSE; + data->component_state = NICE_COMPONENT_STATE_DISCONNECTED; + + g_task_set_task_data (task, data, + (GDestroyNotify) ice_gathering_data_free); + + /* Cancellation. */ + if (g_task_return_error_if_cancelled (task)) { + data->task_returned = TRUE; + goto done; + } + + if (cancellable != NULL) { + data->cancelled_handler = + g_cancellable_connect (cancellable, + (GCallback) cancelled_cb, + task, NULL); + } + + /* Connect to signals. */ + data->component_state_changed_handler = + g_signal_connect (agent, "component-state-changed", + (GCallback) component_state_changed_cb, + task); + + if (protocol == G_SOCKET_PROTOCOL_TCP) { + data->reliable_transport_writable_handler = + g_signal_connect (agent, "reliable-transport-writable", + (GCallback) reliable_transport_writable_cb, + task); + } + + /* Start gathering candidates. */ + g_debug ("Gathering candidates."); + ret = nice_agent_gather_candidates (agent, stream_id); + + if (!ret) { + /* This can only fail if the @stream_id is invalid or if one + * of the detected local addresses is invalid (which shouldn’t + * happen if they come from the OS; it might fail if the caller + * has set their own local addresses). */ + g_set_error (&child_error, G_IO_ERROR, + G_IO_ERROR_INVALID_ARGUMENT, + "Error gathering client candidates."); + goto done; + } + +done: + if (child_error != NULL) { + complete_task (task, child_error); + + g_clear_object (&task); + } + + return task; /* transfer */ +} + +/* Check whether the candidate gathering operation has definitely failed. If + * so, complete the #GTask and return %TRUE. Otherwise, return %FALSE. */ +static gboolean +check_for_failure (GTask *task) +{ + IceGatheringData *data; /* unowned */ + + g_return_val_if_fail (G_IS_TASK (task), FALSE); + g_return_val_if_fail (g_task_get_source_tag (task) == + nice_agent_gather_candidates_async, FALSE); + + data = g_task_get_task_data (task); + + if (data->component_state == NICE_COMPONENT_STATE_FAILED && + data->got_final_turn_server && data->got_final_candidate) { + GError *child_error = NULL; + + /* Failure. */ + g_set_error (&child_error, G_IO_ERROR, + G_IO_ERROR_HOST_UNREACHABLE, + "ICE connection failed"); + complete_task (task, child_error); + + return TRUE; + } + + return FALSE; +} + +/* Takes ownership of @error if non-%NULL. */ +static void +complete_task (GTask *task, GError *error) +{ + IceGatheringData *data; /* unowned */ + + g_return_if_fail (G_IS_TASK (task)); + g_return_if_fail (g_task_get_source_tag (task) == + nice_agent_gather_candidates_async); + + data = g_task_get_task_data (task); + + if (data->task_returned) { + g_debug ("Ignoring duplicate task completion."); + g_clear_error (&error); + return; + } + + ice_gathering_data_disconnect_signals (data); + data->task_returned = TRUE; + + if (error != NULL) { + g_task_return_error (task, error); + } else { + g_task_return_boolean (task, TRUE); + } +} + +/** + * nice_agent_gather_candidates_got_final_candidate: + * @task: an ICE negotiation #GTask + * + * Notify the @task that the final remote candidate has been added to the + * #NiceAgent using nice_agent_set_remote_candidates(). + * + * @task must be an ongoing operation returned by + * nice_agent_gather_candidates_async(). + * + * Since: UNRELEASED + */ +void +nice_agent_gather_candidates_got_final_candidate (GTask *task) +{ + IceGatheringData *data; /* unowned */ + + g_return_if_fail (G_IS_TASK (task)); + g_return_if_fail (g_task_get_source_tag (task) == + nice_agent_gather_candidates_async); + + data = g_task_get_task_data (task); + + g_warn_if_fail (!data->got_final_candidate); + + g_debug ("Received final candidate."); + + data->got_final_candidate = TRUE; + check_for_failure (task); +} + +/** + * nice_agent_gather_candidates_got_final_turn_server: + * @task: an ICE negotiation #GTask + * + * Notify the @task that the final TURN server has been added to the #NiceAgent + * using nice_agent_set_relay_info(). + * + * @task must be an ongoing operation returned by + * nice_agent_gather_candidates_async(). + * + * Since: UNRELEASED + */ +void +nice_agent_gather_candidates_got_final_turn_server (GTask *task) +{ + IceGatheringData *data; /* unowned */ + + g_return_if_fail (G_IS_TASK (task)); + g_return_if_fail (g_task_get_source_tag (task) == + nice_agent_gather_candidates_async); + + data = g_task_get_task_data (task); + + g_warn_if_fail (!data->got_final_turn_server); + + g_debug ("Received final TURN server."); + + data->got_final_turn_server = TRUE; + check_for_failure (task); +} + +/** + * nice_agent_gather_candidates_finish: + * @agent: a #NiceAgent + * @result: asynchronous result + * @error: return location for a #GError, or %NULL + * + * Finish an asynchronous candidate gathering operation started with + * nice_agent_gather_candidates_async(). + * + * Since: UNRELEASED + */ +void +nice_agent_gather_candidates_finish (NiceAgent *agent, GAsyncResult *result, + GError **error) +{ + g_return_if_fail (NICE_IS_AGENT (agent)); + g_return_if_fail (g_task_is_valid (result, agent)); + g_return_if_fail (g_task_get_source_tag (G_TASK (result)) == + nice_agent_gather_candidates_async); + g_return_if_fail (error == NULL || *error == NULL); + + g_task_propagate_boolean (G_TASK (result), error); +} diff --git a/agent/async.h b/agent/async.h new file mode 100644 index 0000000..c17383d --- /dev/null +++ b/agent/async.h @@ -0,0 +1,64 @@ +/* + * This file is part of the Nice GLib ICE library. + * + * (C) 2014 Collabora Ltd. + * Contact: Philip Withnall + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is the Nice GLib ICE library. + * + * The Initial Developers of the Original Code are Collabora Ltd and Nokia + * Corporation. All Rights Reserved. + * + * Contributors: + * Philip Withnall, Collabora Ltd. + * + * Alternatively, the contents of this file may be used under the terms of the + * the GNU Lesser General Public License Version 2.1 (the "LGPL"), in which + * case the provisions of LGPL are applicable instead of those above. If you + * wish to allow use of your version of this file only under the terms of the + * LGPL and not to allow others to use your version of this file under the + * MPL, indicate your decision by deleting the provisions above and replace + * them with the notice and other provisions required by the LGPL. If you do + * not delete the provisions above, a recipient may use your version of this + * file under either the MPL or the LGPL. + */ + +#include + +#include +#include + +#include "agent.h" + + +#ifndef _NICE_ASYNC_H_ +#define _NICE_ASYNC_H_ + + +GTask * +nice_agent_gather_candidates_async (NiceAgent *agent, guint stream_id, + guint component_id, + GSocketProtocol protocol, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +void +nice_agent_gather_candidates_got_final_candidate (GTask *task); +void +nice_agent_gather_candidates_got_final_turn_server (GTask *task); +void +nice_agent_gather_candidates_finish (NiceAgent *agent, GAsyncResult *result, + GError **error); + + +#endif /* _NICE_ASYNC_H_ */ diff --git a/docs/reference/libnice/Makefile.am b/docs/reference/libnice/Makefile.am index 36ac119..1e89507 100644 --- a/docs/reference/libnice/Makefile.am +++ b/docs/reference/libnice/Makefile.am @@ -39,6 +39,7 @@ FIXXREF_OPTIONS= # e.g. CFILE_GLOB=$(top_srcdir)/gtk/*.c HFILE_GLOB=$(top_srcdir)/agent/agent.h $(top_srcdir)/agent/address.h \ $(top_srcdir)/agent/debug.h $(top_srcdir)/agent/candidate.h \ + $(top_srcdir)/agent/async.h \ $(top_srcdir)/agent/interfaces.h \ $(top_srcdir)/agent/pseudotcp.h \ $(top_srcdir)/stun/stunagent.h \ diff --git a/docs/reference/libnice/libnice-sections.txt b/docs/reference/libnice/libnice-sections.txt index dd185f0..279d94b 100644 --- a/docs/reference/libnice/libnice-sections.txt +++ b/docs/reference/libnice/libnice-sections.txt @@ -19,6 +19,10 @@ nice_agent_remove_stream nice_agent_set_relay_info nice_agent_forget_relays nice_agent_gather_candidates +nice_agent_gather_candidates_async +nice_agent_gather_candidates_got_final_candidate +nice_agent_gather_candidates_got_final_turn_server +nice_agent_gather_candidates_finish nice_agent_set_remote_credentials nice_agent_get_local_credentials nice_agent_set_remote_candidates diff --git a/nice/libnice.sym b/nice/libnice.sym index 117d00a..a05d330 100644 --- a/nice/libnice.sym +++ b/nice/libnice.sym @@ -24,6 +24,10 @@ nice_agent_recv_messages_nonblocking nice_agent_attach_recv nice_agent_forget_relays nice_agent_gather_candidates +nice_agent_gather_candidates_async +nice_agent_gather_candidates_got_final_candidate +nice_agent_gather_candidates_got_final_turn_server +nice_agent_gather_candidates_finish nice_agent_generate_local_candidate_sdp nice_agent_generate_local_sdp nice_agent_generate_local_stream_sdp diff --git a/nice/nice.h b/nice/nice.h index 5587ec4..a679be9 100644 --- a/nice/nice.h +++ b/nice/nice.h @@ -40,6 +40,7 @@ #define _NICE_H #include "agent.h" +#include "async.h" #include "interfaces.h" #endif /* _NICE_H */ diff --git a/win32/vs9/libnice.def b/win32/vs9/libnice.def index a9cd217..bc4886a 100644 --- a/win32/vs9/libnice.def +++ b/win32/vs9/libnice.def @@ -24,6 +24,10 @@ nice_agent_add_stream nice_agent_attach_recv nice_agent_forget_relays nice_agent_gather_candidates +nice_agent_gather_candidates_async +nice_agent_gather_candidates_got_final_candidate +nice_agent_gather_candidates_got_final_turn_server +nice_agent_gather_candidates_finish nice_agent_generate_local_candidate_sdp nice_agent_generate_local_sdp nice_agent_generate_local_stream_sdp diff --git a/win32/vs9/libnice.vcproj b/win32/vs9/libnice.vcproj index a4c6bde..f8b5c2a 100644 --- a/win32/vs9/libnice.vcproj +++ b/win32/vs9/libnice.vcproj @@ -199,6 +199,14 @@ > + + + + -- 1.8.3.1