From 49bb7cff4b2f238453363b032f2c401055158026 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Sun, 28 Aug 2011 20:24:56 +0200 Subject: gio: Add GTlsInteraction interaction method invocation guarantees * Add 'invoke' style method, which can be used to call an interaction from any thread. The interaction will be run in the appropriate #GMainContext * Sync methods can be called whether main loop is running or not. * Derived classes can choose to implement only sync or async interaction method, and the invoke method will fill in the blanks. * Documentation for the above. * Tests for the above. https://bugzilla.gnome.org/show_bug.cgi?id=657567 --- .gitignore | 1 + gio/gio.symbols | 1 + gio/gtlsinteraction.c | 423 +++++++++++++++++++++++----- gio/gtlsinteraction.h | 12 +- gio/tests/Makefile.am | 3 + gio/tests/tls-interaction.c | 661 ++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1036 insertions(+), 65 deletions(-) create mode 100644 gio/tests/tls-interaction.c diff --git a/.gitignore b/.gitignore index 5c1278b2f..46f1aae10 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ README ChangeLog /glib-lcov.info /glib-lcov/ +/gio/tests/tls-interaction diff --git a/gio/gio.symbols b/gio/gio.symbols index 4821380b7..945147028 100644 --- a/gio/gio.symbols +++ b/gio/gio.symbols @@ -1486,6 +1486,7 @@ g_tls_interaction_get_type g_tls_interaction_ask_password g_tls_interaction_ask_password_async g_tls_interaction_ask_password_finish +g_tls_interaction_invoke_ask_password g_tls_interaction_result_get_type g_tls_password_flags_get_type g_tls_password_get_type diff --git a/gio/gtlsinteraction.c b/gio/gtlsinteraction.c index 3c8be535a..28c3f1144 100644 --- a/gio/gtlsinteraction.c +++ b/gio/gtlsinteraction.c @@ -44,10 +44,20 @@ * To use a #GTlsInteraction with a TLS connection use * g_tls_connection_set_interaction(). * - * Callers should instantiate a subclass of this that implements all the - * various callbacks to show the required dialogs, such as - * #GtkTlsInteraction. If no interaction is desired, usually %NULL can be - * passed, see each method taking a #GTlsInteraction for details. + * Callers should instantiate a derived class that implements the various + * interaction methods to show the required dialogs. + * + * Callers should use the 'invoke' functions like + * g_tls_interaction_invoke_ask_password() to run interaction methods. These + * functions make sure that the interaction is invoked in the main loop + * and not in the current thread, if the current thread is not running the + * main loop. + * + * Derived classes can choose to implement whichever interactions methods they'd + * like to support by overriding those virtual methods in their class + * initialization function. Any interactions not implemented will return + * %G_TLS_INTERACTION_UNHANDLED. If a derived class implements an async method, + * it must also implement the corresponding finish method. */ /** @@ -61,71 +71,323 @@ /** * GTlsInteractionClass: + * @ask_password: ask for a password synchronously. If the implementation + * returns %G_TLS_INTERACTION_HANDLED, then the password argument should + * have been filled in by using g_tls_password_set_value() or a similar + * function. + * @ask_password_async: ask for a password asynchronously. + * @ask_password_finish: complete operation to ask for a password asynchronously. + * If the implementation returns %G_TLS_INTERACTION_HANDLED, then the + * password argument of the async method should have been filled in by using + * g_tls_password_set_value() or a similar function. + * + * The class for #GTlsInteraction. Derived classes implement the various + * virtual interaction methods to handle TLS interactions. + * + * Derived classes can choose to implement whichever interactions methods they'd + * like to support by overriding those virtual methods in their class + * initialization function. If a derived class implements an async method, + * it must also implement the corresponding finish method. * - * The class for #GTlsInteraction. + * The synchronous interaction methods should implement to display modal dialogs, + * and the asynchronous methods to display modeless dialogs. + * + * If the user cancels an interaction, then the result should be + * %G_TLS_INTERACTION_FAILED and the error should be set with a domain of + * %G_IO_ERROR and code of %G_IO_ERROR_CANCELLED. * * Since: 2.30 */ +struct _GTlsInteractionPrivate { + GMainContext *context; +}; + G_DEFINE_TYPE (GTlsInteraction, g_tls_interaction, G_TYPE_OBJECT); -static GTlsInteractionResult -g_tls_interaction_default_ask_password (GTlsInteraction *interaction, - GTlsPassword *password, - GCancellable *cancellable, - GError **error) +typedef struct { + GMutex *mutex; + + /* Input arguments */ + GTlsInteraction *interaction; + GObject *argument; + GCancellable *cancellable; + + /* Used when we're invoking async interactions */ + GAsyncReadyCallback callback; + gpointer user_data; + + /* Used when we expect results */ + GTlsInteractionResult result; + GError *error; + gboolean complete; + GCond *cond; +} InvokeClosure; + +static void +invoke_closure_free (gpointer data) { - if (g_cancellable_set_error_if_cancelled (cancellable, error)) - return G_TLS_INTERACTION_FAILED; - else - return G_TLS_INTERACTION_UNHANDLED; + InvokeClosure *closure = data; + g_assert (closure); + g_object_unref (closure->interaction); + g_clear_object (&closure->argument); + g_clear_object (&closure->cancellable); + g_cond_free (closure->cond); + g_mutex_free (closure->mutex); + g_clear_error (&closure->error); + + /* Insurance that we've actually used these before freeing */ + g_assert (closure->callback == NULL); + g_assert (closure->user_data == NULL); + + g_free (closure); } -static void -g_tls_interaction_default_ask_password_async (GTlsInteraction *interaction, - GTlsPassword *password, - GCancellable *cancellable, - GAsyncReadyCallback callback, - gpointer user_data) +static InvokeClosure * +invoke_closure_new (GTlsInteraction *interaction, + GObject *argument, + GCancellable *cancellable) { - GSimpleAsyncResult *res; - GError *error = NULL; - - res = g_simple_async_result_new (G_OBJECT (interaction), callback, user_data, - g_tls_interaction_default_ask_password); - if (g_cancellable_set_error_if_cancelled (cancellable, &error)) - g_simple_async_result_take_error (res, error); - g_simple_async_result_complete_in_idle (res); - g_object_unref (res); + InvokeClosure *closure = g_new0 (InvokeClosure, 1); + closure->interaction = g_object_ref (interaction); + closure->argument = argument ? g_object_ref (argument) : NULL; + closure->cancellable = cancellable ? g_object_ref (cancellable) : NULL; + closure->mutex = g_mutex_new (); + closure->cond = g_cond_new (); + closure->result = G_TLS_INTERACTION_UNHANDLED; + return closure; } static GTlsInteractionResult -g_tls_interaction_default_ask_password_finish (GTlsInteraction *interaction, - GAsyncResult *result, - GError **error) +invoke_closure_wait_and_free (InvokeClosure *closure, + GError **error) { - g_return_val_if_fail (g_simple_async_result_is_valid (result, G_OBJECT (interaction), - g_tls_interaction_default_ask_password), G_TLS_INTERACTION_UNHANDLED); + GTlsInteractionResult result; + + g_mutex_lock (closure->mutex); + + while (!closure->complete) + g_cond_wait (closure->cond, closure->mutex); - if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error)) - return G_TLS_INTERACTION_FAILED; + g_mutex_unlock (closure->mutex); - return G_TLS_INTERACTION_UNHANDLED; + if (closure->error) + { + g_propagate_error (error, closure->error); + closure->error = NULL; + } + result = closure->result; + + invoke_closure_free (closure); + return result; } static void g_tls_interaction_init (GTlsInteraction *interaction) { + interaction->priv = G_TYPE_INSTANCE_GET_PRIVATE (interaction, G_TYPE_TLS_INTERACTION, + GTlsInteractionPrivate); + interaction->priv->context = g_main_context_get_thread_default (); + if (interaction->priv->context) + g_main_context_ref (interaction->priv->context); +} + +static void +g_tls_interaction_finalize (GObject *object) +{ + GTlsInteraction *interaction = G_TLS_INTERACTION (object); + + if (interaction->priv->context) + g_main_context_unref (interaction->priv->context); + + G_OBJECT_CLASS (g_tls_interaction_parent_class)->finalize (object); } static void g_tls_interaction_class_init (GTlsInteractionClass *klass) { - klass->ask_password = g_tls_interaction_default_ask_password; - klass->ask_password_async = g_tls_interaction_default_ask_password_async; - klass->ask_password_finish = g_tls_interaction_default_ask_password_finish; + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = g_tls_interaction_finalize; + + g_type_class_add_private (klass, sizeof (GTlsInteractionPrivate)); +} + +static gboolean +on_invoke_ask_password_sync (gpointer user_data) +{ + InvokeClosure *closure = user_data; + GTlsInteractionClass *klass; + + g_mutex_lock (closure->mutex); + + klass = G_TLS_INTERACTION_GET_CLASS (closure->interaction); + g_assert (klass->ask_password); + + closure->result = klass->ask_password (closure->interaction, + G_TLS_PASSWORD (closure->argument), + closure->cancellable, + &closure->error); + + closure->complete = TRUE; + g_cond_signal (closure->cond); + g_mutex_unlock (closure->mutex); + + return FALSE; /* don't call again */ +} + +static void +on_async_as_sync_complete (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + InvokeClosure *closure = user_data; + GTlsInteractionClass *klass; + + g_mutex_lock (closure->mutex); + + klass = G_TLS_INTERACTION_GET_CLASS (closure->interaction); + g_assert (klass->ask_password_finish); + + closure->result = klass->ask_password_finish (closure->interaction, + result, + &closure->error); + + closure->complete = TRUE; + g_cond_signal (closure->cond); + g_mutex_unlock (closure->mutex); +} + +static gboolean +on_invoke_ask_password_async_as_sync (gpointer user_data) +{ + InvokeClosure *closure = user_data; + GTlsInteractionClass *klass; + + g_mutex_lock (closure->mutex); + + klass = G_TLS_INTERACTION_GET_CLASS (closure->interaction); + g_assert (klass->ask_password_async); + + klass->ask_password_async (closure->interaction, + G_TLS_PASSWORD (closure->argument), + closure->cancellable, + on_async_as_sync_complete, + closure); + + /* Note that we've used these */ + closure->callback = NULL; + closure->user_data = NULL; + + g_mutex_unlock (closure->mutex); + + return FALSE; /* don't call again */ +} + +/** + * g_tls_interaction_invoke_ask_password: + * @interaction: a #GTlsInteraction object + * @password: a #GTlsPassword object + * @cancellable: an optional #GCancellable cancellation object + * @error: an optional location to place an error on failure + * + * Invoke the interaction to ask the user for a password. It invokes this + * interaction in the main loop, specifically the #GMainContext returned by + * g_main_context_get_thread_default() when the interaction is created. This + * is called by called by #GTlsConnection or #GTlsDatabase to ask the user + * for a password. + * + * Derived subclasses usually implement a password prompt, although they may + * also choose to provide a password from elsewhere. The @password value will + * be filled in and then @callback will be called. Alternatively the user may + * abort this password request, which will usually abort the TLS connection. + * + * The implementation can either be a synchronous (eg: modal dialog) or an + * asynchronous one (eg: modeless dialog). This function will take care of + * calling which ever one correctly. + * + * If the interaction is cancelled by the cancellation object, or by the + * user then %G_TLS_INTERACTION_FAILED will be returned with an error that + * contains a %G_IO_ERROR_CANCELLED error code. Certain implementations may + * not support immediate cancellation. + * + * Returns: The status of the ask password interaction. + * + * Since: 2.30 + */ +GTlsInteractionResult +g_tls_interaction_invoke_ask_password (GTlsInteraction *interaction, + GTlsPassword *password, + GCancellable *cancellable, + GError **error) +{ + GTlsInteractionResult result; + InvokeClosure *closure; + GTlsInteractionClass *klass; + + g_return_val_if_fail (G_IS_TLS_INTERACTION (interaction), G_TLS_INTERACTION_UNHANDLED); + g_return_val_if_fail (G_IS_TLS_PASSWORD (password), G_TLS_INTERACTION_UNHANDLED); + g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), G_TLS_INTERACTION_UNHANDLED); + + closure = invoke_closure_new (interaction, G_OBJECT (password), cancellable); + + klass = G_TLS_INTERACTION_GET_CLASS (interaction); + if (klass->ask_password) + { + g_main_context_invoke (interaction->priv->context, + on_invoke_ask_password_sync, closure); + result = invoke_closure_wait_and_free (closure, error); + } + else if (klass->ask_password_async) + { + g_return_val_if_fail (klass->ask_password_finish, G_TLS_INTERACTION_UNHANDLED); + g_main_context_invoke (interaction->priv->context, + on_invoke_ask_password_async_as_sync, closure); + + /* + * Handle the case where we've been called from within the main context + * or in the case where the main context is not running. This approximates + * the behavior of a modal dialog. + */ + if (g_main_context_acquire (interaction->priv->context)) + { + while (!closure->complete) + { + g_mutex_unlock (closure->mutex); + g_main_context_iteration (interaction->priv->context, TRUE); + g_mutex_lock (closure->mutex); + } + g_main_context_release (interaction->priv->context); + + if (closure->error) + { + g_propagate_error (error, closure->error); + closure->error = NULL; + } + + result = closure->result; + invoke_closure_free (closure); + } + + /* + * Handle the case where we're in a different thread than the main + * context and a main loop is running. + */ + else + { + result = invoke_closure_wait_and_free (closure, error); + } + } + else + { + result = G_TLS_INTERACTION_UNHANDLED; + invoke_closure_free (closure); + } + + return result; } + /** * g_tls_interaction_ask_password: * @interaction: a #GTlsInteraction object @@ -133,8 +395,9 @@ g_tls_interaction_class_init (GTlsInteractionClass *klass) * @cancellable: an optional #GCancellable cancellation object * @error: an optional location to place an error on failure * - * This function is normally called by #GTlsConnection or #GTlsDatabase to - * ask the user for a password. + * Run synchronous interaction to ask the user for a password. In general, + * g_tls_interaction_invoke_ask_password() should be used instead of this + * function. * * Derived subclasses usually implement a password prompt, although they may * also choose to provide a password from elsewhere. The @password value will @@ -142,8 +405,9 @@ g_tls_interaction_class_init (GTlsInteractionClass *klass) * abort this password request, which will usually abort the TLS connection. * * If the interaction is cancelled by the cancellation object, or by the - * user then %G_TLS_INTERACTION_ABORTED will be returned. Certain - * implementations may not support immediate cancellation. + * user then %G_TLS_INTERACTION_FAILED will be returned with an error that + * contains a %G_IO_ERROR_CANCELLED error code. Certain implementations may + * not support immediate cancellation. * * Returns: The status of the ask password interaction. * @@ -155,13 +419,17 @@ g_tls_interaction_ask_password (GTlsInteraction *interaction, GCancellable *cancellable, GError **error) { + GTlsInteractionClass *klass; + g_return_val_if_fail (G_IS_TLS_INTERACTION (interaction), G_TLS_INTERACTION_UNHANDLED); g_return_val_if_fail (G_IS_TLS_PASSWORD (password), G_TLS_INTERACTION_UNHANDLED); g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), G_TLS_INTERACTION_UNHANDLED); - return G_TLS_INTERACTION_GET_CLASS (interaction)->ask_password (interaction, - password, - cancellable, - error); + + klass = G_TLS_INTERACTION_GET_CLASS (interaction); + if (klass->ask_password) + return (klass->ask_password) (interaction, password, cancellable, error); + else + return G_TLS_INTERACTION_UNHANDLED; } /** @@ -172,18 +440,19 @@ g_tls_interaction_ask_password (GTlsInteraction *interaction, * @callback: (allow-none): will be called when the interaction completes * @user_data: (allow-none): data to pass to the @callback * - * This function is normally called by #GTlsConnection or #GTlsDatabase to - * ask the user for a password. + * Run asynchronous interaction to ask the user for a password. In general, + * g_tls_interaction_invoke_ask_password() should be used instead of this + * function. * * Derived subclasses usually implement a password prompt, although they may * also choose to provide a password from elsewhere. The @password value will * be filled in and then @callback will be called. Alternatively the user may * abort this password request, which will usually abort the TLS connection. * - * The @callback will be invoked on thread-default main context of the thread - * that called this function. The @callback should call - * g_tls_interaction_ask_password_finish() to get the status of the user - * interaction. + * If the interaction is cancelled by the cancellation object, or by the + * user then %G_TLS_INTERACTION_FAILED will be returned with an error that + * contains a %G_IO_ERROR_CANCELLED error code. Certain implementations may + * not support immediate cancellation. * * Certain implementations may not support immediate cancellation. * @@ -196,12 +465,27 @@ g_tls_interaction_ask_password_async (GTlsInteraction *interaction, GAsyncReadyCallback callback, gpointer user_data) { + GTlsInteractionClass *klass; + GSimpleAsyncResult *res; + g_return_if_fail (G_IS_TLS_INTERACTION (interaction)); g_return_if_fail (G_IS_TLS_PASSWORD (password)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); - G_TLS_INTERACTION_GET_CLASS (interaction)->ask_password_async (interaction, password, - cancellable, - callback, user_data); + + klass = G_TLS_INTERACTION_GET_CLASS (interaction); + if (klass->ask_password_async) + { + g_return_if_fail (klass->ask_password_finish); + (klass->ask_password_async) (interaction, password, cancellable, + callback, user_data); + } + else + { + res = g_simple_async_result_new (G_OBJECT (interaction), callback, user_data, + g_tls_interaction_ask_password_async); + g_simple_async_result_complete_in_idle (res); + g_object_unref (res); + } } /** @@ -211,13 +495,14 @@ g_tls_interaction_ask_password_async (GTlsInteraction *interaction, * @error: an optional location to place an error on failure * * Complete an ask password user interaction request. This should be once - * the g_tls_interaction_ask_password() completion callback is called. + * the g_tls_interaction_ask_password_async() completion callback is called. * * If %G_TLS_INTERACTION_HANDLED is returned, then the #GTlsPassword passed * to g_tls_interaction_ask_password() will have its password filled in. * * If the interaction is cancelled by the cancellation object, or by the - * user then %G_TLS_INTERACTION_ABORTED will be returned. + * user then %G_TLS_INTERACTION_FAILED will be returned with an error that + * contains a %G_IO_ERROR_CANCELLED error code. * * Returns: The status of the ask password interaction. * @@ -228,9 +513,23 @@ g_tls_interaction_ask_password_finish (GTlsInteraction *interaction, GAsyncResult *result, GError **error) { + GTlsInteractionClass *klass; + g_return_val_if_fail (G_IS_TLS_INTERACTION (interaction), G_TLS_INTERACTION_UNHANDLED); g_return_val_if_fail (G_IS_ASYNC_RESULT (result), G_TLS_INTERACTION_UNHANDLED); - return G_TLS_INTERACTION_GET_CLASS (interaction)->ask_password_finish (interaction, - result, - error); + + /* If it's one of our simple unhandled async results, handle here */ + if (g_simple_async_result_is_valid (result, G_OBJECT (interaction), + g_tls_interaction_ask_password_async)) + { + return G_TLS_INTERACTION_UNHANDLED; + } + + /* Invoke finish of derived class */ + else + { + klass = G_TLS_INTERACTION_GET_CLASS (interaction); + g_return_val_if_fail (klass->ask_password_finish, G_TLS_INTERACTION_UNHANDLED); + return (klass->ask_password_finish) (interaction, result, error); + } } diff --git a/gio/gtlsinteraction.h b/gio/gtlsinteraction.h index 06fb52e43..fc76fe2df 100644 --- a/gio/gtlsinteraction.h +++ b/gio/gtlsinteraction.h @@ -43,17 +43,17 @@ typedef struct _GTlsInteractionPrivate GTlsInteractionPrivate; struct _GTlsInteraction { + /*< private >*/ GObject parent_instance; - GTlsInteractionPrivate *priv; }; struct _GTlsInteractionClass { + /*< private >*/ GObjectClass parent_class; - /* virtual methods: */ - + /*< public >*/ GTlsInteractionResult (* ask_password) (GTlsInteraction *interaction, GTlsPassword *password, GCancellable *cancellable, @@ -76,6 +76,12 @@ struct _GTlsInteractionClass GType g_tls_interaction_get_type (void) G_GNUC_CONST; +GTlsInteractionResult g_tls_interaction_invoke_ask_password (GTlsInteraction *interaction, + GTlsPassword *password, + GCancellable *cancellable, + GError **error); + + GTlsInteractionResult g_tls_interaction_ask_password (GTlsInteraction *interaction, GTlsPassword *password, GCancellable *cancellable, diff --git a/gio/tests/Makefile.am b/gio/tests/Makefile.am index fd820dcd2..a85ea4f21 100644 --- a/gio/tests/Makefile.am +++ b/gio/tests/Makefile.am @@ -52,6 +52,7 @@ TEST_PROGS += \ socket \ pollable \ tls-certificate \ + tls-interaction \ cancellable \ $(NULL) @@ -468,6 +469,8 @@ proxy_LDADD = $(progs_ldadd) \ tls_certificate_SOURCES = tls-certificate.c gtesttlsbackend.c gtesttlsbackend.h tls_certificate_LDADD = $(progs_ldadd) +tls_interaction_LDADD = $(progs_ldadd) + cancellable_LDADD = $(progs_ldadd) # ----------------------------------------------------------------------------- diff --git a/gio/tests/tls-interaction.c b/gio/tests/tls-interaction.c new file mode 100644 index 000000000..242a92e3d --- /dev/null +++ b/gio/tests/tls-interaction.c @@ -0,0 +1,661 @@ +/* GLib testing framework examples and tests + * + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place, Suite 330, + * Boston, MA 02111-1307, USA. + * + * Author: Stef Walter + */ + +#include "config.h" + +#include + +typedef struct { + /* Class virtual interaction methods */ + gpointer ask_password_func; + gpointer ask_password_async_func; + gpointer ask_password_finish_func; + + /* Expected results */ + GTlsInteractionResult result; + GQuark error_domain; + gint error_code; + const gchar *error_message; +} Fixture; + +typedef struct { + GTlsInteraction *interaction; + GTlsPassword *password; + GMainLoop *loop; + GThread *interaction_thread; + GThread *test_thread; + GThread *loop_thread; + const Fixture *fixture; +} Test; + +typedef struct { + GTlsInteraction parent; + Test *test; +} TestInteraction; + +typedef struct { + GTlsInteractionClass parent; +} TestInteractionClass; + +G_DEFINE_TYPE (TestInteraction, test_interaction, G_TYPE_TLS_INTERACTION); + +#define TEST_TYPE_INTERACTION (test_interaction_get_type ()) +#define TEST_INTERACTION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), TEST_TYPE_INTERACTION, TestInteraction)) +#define TEST_IS_INTERACTION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), TEST_TYPE_INTERACTION)) + +static void +test_interaction_init (TestInteraction *self) +{ + +} + +static void +test_interaction_class_init (TestInteractionClass *klass) +{ + /* By default no virtual methods */ +} + +static void +test_interaction_ask_password_async_success (GTlsInteraction *interaction, + GTlsPassword *password, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (G_IS_TLS_PASSWORD (password)); + g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + test_interaction_ask_password_async_success); + + /* Don't do this in real life. Include a null terminator for testing */ + g_tls_password_set_value (password, (const guchar *)"the password", 13); + g_simple_async_result_complete_in_idle (res); + g_object_unref (res); +} + + +static GTlsInteractionResult +test_interaction_ask_password_finish_success (GTlsInteraction *interaction, + GAsyncResult *result, + GError **error) +{ + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (g_simple_async_result_is_valid (result, G_OBJECT (interaction), + test_interaction_ask_password_async_success)); + g_assert (error != NULL); + g_assert (*error == NULL); + + return G_TLS_INTERACTION_HANDLED; +} + +static void +test_interaction_ask_password_async_failure (GTlsInteraction *interaction, + GTlsPassword *password, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + GSimpleAsyncResult *res; + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (G_IS_TLS_PASSWORD (password)); + g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + + res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + test_interaction_ask_password_async_failure); + + g_simple_async_result_set_error (res, G_FILE_ERROR, G_FILE_ERROR_ACCES, "The message"); + g_simple_async_result_complete_in_idle (res); + g_object_unref (res); +} + +static GTlsInteractionResult +test_interaction_ask_password_finish_failure (GTlsInteraction *interaction, + GAsyncResult *result, + GError **error) +{ + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (g_simple_async_result_is_valid (result, G_OBJECT (interaction), + test_interaction_ask_password_async_failure)); + g_assert (error != NULL); + g_assert (*error == NULL); + + if (!g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error)) + g_assert_not_reached (); + return G_TLS_INTERACTION_FAILED; +} + + +static GTlsInteractionResult +test_interaction_ask_password_sync_success (GTlsInteraction *interaction, + GTlsPassword *password, + GCancellable *cancellable, + GError **error) +{ + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (G_IS_TLS_PASSWORD (password)); + g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_assert (error != NULL); + g_assert (*error == NULL); + + /* Don't do this in real life. Include a null terminator for testing */ + g_tls_password_set_value (password, (const guchar *)"the password", 13); + return G_TLS_INTERACTION_HANDLED; +} + +static GTlsInteractionResult +test_interaction_ask_password_sync_failure (GTlsInteraction *interaction, + GTlsPassword *password, + GCancellable *cancellable, + GError **error) +{ + TestInteraction *self; + + g_assert (TEST_IS_INTERACTION (interaction)); + self = TEST_INTERACTION (interaction); + + g_assert (g_thread_self () == self->test->interaction_thread); + + g_assert (G_IS_TLS_PASSWORD (password)); + g_assert (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); + g_assert (error != NULL); + g_assert (*error == NULL); + + g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_ACCES, "The message"); + return G_TLS_INTERACTION_FAILED; +} + +/* ---------------------------------------------------------------------------- + * ACTUAL TESTS + */ + +static void +on_ask_password_async_call (GObject *source, + GAsyncResult *result, + gpointer user_data) +{ + Test *test = user_data; + GTlsInteractionResult res; + GError *error = NULL; + + g_assert (G_IS_TLS_INTERACTION (source)); + g_assert (G_TLS_INTERACTION (source) == test->interaction); + + /* Check that this callback is being run in the right place */ + g_assert (g_thread_self () == test->interaction_thread); + + res = g_tls_interaction_ask_password_finish (test->interaction, result, + &error); + + /* Check that the results match the fixture */ + g_assert_cmpuint (test->fixture->result, ==, res); + switch (test->fixture->result) + { + case G_TLS_INTERACTION_HANDLED: + g_assert_no_error (error); + g_assert_cmpstr ((const gchar *)g_tls_password_get_value (test->password, NULL), ==, "the password"); + break; + case G_TLS_INTERACTION_FAILED: + g_assert_error (error, test->fixture->error_domain, test->fixture->error_code); + g_assert_cmpstr (error->message, ==, test->fixture->error_message); + g_clear_error (&error); + break; + case G_TLS_INTERACTION_UNHANDLED: + g_assert_no_error (error); + break; + default: + g_assert_not_reached (); + } + + /* Signal the end of the test */ + g_main_loop_quit (test->loop); +} + +static void +test_ask_password_async (Test *test, + gconstpointer unused) +{ + /* This test only works with a main loop */ + g_assert (test->loop); + + g_tls_interaction_ask_password_async (test->interaction, + test->password, NULL, + on_ask_password_async_call, + test); + + /* teardown waits until g_main_loop_quit(). called from callback */ +} + +static void +test_invoke_ask_password (Test *test, + gconstpointer unused) +{ + GTlsInteractionResult res; + GError *error = NULL; + + res = g_tls_interaction_invoke_ask_password (test->interaction, test->password, + NULL, &error); + + /* Check that the results match the fixture */ + g_assert_cmpuint (test->fixture->result, ==, res); + switch (test->fixture->result) + { + case G_TLS_INTERACTION_HANDLED: + g_assert_no_error (error); + g_assert_cmpstr ((const gchar *)g_tls_password_get_value (test->password, NULL), ==, "the password"); + break; + case G_TLS_INTERACTION_FAILED: + g_assert_error (error, test->fixture->error_domain, test->fixture->error_code); + g_assert_cmpstr (error->message, ==, test->fixture->error_message); + g_clear_error (&error); + break; + case G_TLS_INTERACTION_UNHANDLED: + g_assert_no_error (error); + break; + default: + g_assert_not_reached (); + } + + /* This allows teardown to stop if running with loop */ + if (test->loop) + g_main_loop_quit (test->loop); +} + +static void +test_ask_password (Test *test, + gconstpointer unused) +{ + GTlsInteractionResult res; + GError *error = NULL; + + res = g_tls_interaction_ask_password (test->interaction, test->password, + NULL, &error); + + /* Check that the results match the fixture */ + g_assert_cmpuint (test->fixture->result, ==, res); + switch (test->fixture->result) + { + case G_TLS_INTERACTION_HANDLED: + g_assert_no_error (error); + g_assert_cmpstr ((const gchar *)g_tls_password_get_value (test->password, NULL), ==, "the password"); + break; + case G_TLS_INTERACTION_FAILED: + g_assert_error (error, test->fixture->error_domain, test->fixture->error_code); + g_assert_cmpstr (error->message, ==, test->fixture->error_message); + g_clear_error (&error); + break; + case G_TLS_INTERACTION_UNHANDLED: + g_assert_no_error (error); + break; + default: + g_assert_not_reached (); + } + + /* This allows teardown to stop if running with loop */ + if (test->loop) + g_main_loop_quit (test->loop); +} + +/* ---------------------------------------------------------------------------- + * TEST SETUP + */ + +static void +setup_without_loop (Test *test, + gconstpointer user_data) +{ + const Fixture *fixture = user_data; + GTlsInteractionClass *klass; + test->fixture = fixture; + + test->interaction = g_object_new (TEST_TYPE_INTERACTION, NULL); + g_assert (TEST_IS_INTERACTION (test->interaction)); + + TEST_INTERACTION (test->interaction)->test = test; + + klass = G_TLS_INTERACTION_GET_CLASS (test->interaction); + klass->ask_password = fixture->ask_password_func; + klass->ask_password_async = fixture->ask_password_async_func; + klass->ask_password_finish = fixture->ask_password_finish_func; + + test->password = g_tls_password_new (0, "Description"); + test->test_thread = g_thread_self (); + + /* + * If no loop is running then interaction should happen in the same + * thread that the tests are running in. + */ + test->interaction_thread = test->test_thread; +} + +static void +teardown_without_loop (Test *test, + gconstpointer unused) +{ + g_object_unref (test->password); + + g_object_unref (test->interaction); + g_assert (!G_IS_OBJECT (test->interaction)); + +} + +typedef struct { + GMutex *loop_mutex; + GCond *loop_started; + Test *test; +} ThreadLoop; + +static gpointer +thread_loop (gpointer user_data) +{ + GMainContext *context = g_main_context_default (); + ThreadLoop *closure = user_data; + Test *test = closure->test; + + g_mutex_lock (closure->loop_mutex); + + g_assert (test->loop_thread == g_thread_self ()); + g_assert (test->loop == NULL); + test->loop = g_main_loop_new (context, TRUE); + + g_cond_signal (closure->loop_started); + g_mutex_unlock (closure->loop_mutex); + + while (g_main_loop_is_running (test->loop)) + g_main_context_iteration (context, TRUE); + + g_main_loop_unref (test->loop); + test->loop = NULL; + return test; +} + +static void +setup_with_thread_loop (Test *test, + gconstpointer user_data) +{ + GError *error = NULL; + ThreadLoop closure; + + setup_without_loop (test, user_data); + + closure.loop_mutex = g_mutex_new (); + closure.loop_started = g_cond_new (); + closure.test = test; + + g_mutex_lock (closure.loop_mutex); + test->loop_thread = g_thread_create (thread_loop, &closure, TRUE, &error); + g_cond_wait (closure.loop_started, closure.loop_mutex); + g_mutex_unlock (closure.loop_mutex); + + /* + * When a loop is running then interaction should always occur in the main + * context of that loop. + */ + test->interaction_thread = test->loop_thread; + + g_mutex_free (closure.loop_mutex); + g_cond_free (closure.loop_started); +} + +static void +teardown_with_thread_loop (Test *test, + gconstpointer unused) +{ + gpointer check; + + g_assert (test->loop_thread); + check = g_thread_join (test->loop_thread); + g_assert (check == test); + test->loop_thread = NULL; + + g_assert (test->loop == NULL); + + teardown_without_loop (test, unused); +} + +static void +setup_with_normal_loop (Test *test, + gconstpointer user_data) +{ + GMainContext *context; + + setup_without_loop (test, user_data); + + context = g_main_context_default (); + if (!g_main_context_acquire (context)) + g_assert_not_reached (); + + test->loop = g_main_loop_new (context, TRUE); + g_assert (g_main_loop_is_running (test->loop)); +} + +static void +teardown_with_normal_loop (Test *test, + gconstpointer unused) +{ + GMainContext *context; + + context = g_main_context_default (); + while (g_main_loop_is_running (test->loop)) + g_main_context_iteration (context, TRUE); + + g_main_context_release (context); + + /* Run test until complete */ + g_main_loop_unref (test->loop); + test->loop = NULL; + + teardown_without_loop (test, unused); +} + +typedef void (*TestFunc) (Test *test, gconstpointer data); + +static void +test_with_async_ask_password_implementations (const gchar *name, + TestFunc setup, + TestFunc func, + TestFunc teardown, + GPtrArray *fixtures) +{ + gchar *test_name; + Fixture *fixture; + + /* Async implementation that succeeds */ + fixture = g_new0 (Fixture, 1); + fixture->ask_password_async_func = test_interaction_ask_password_async_success; + fixture->ask_password_finish_func = test_interaction_ask_password_finish_success; + fixture->ask_password_func = NULL; + fixture->result = G_TLS_INTERACTION_HANDLED; + test_name = g_strdup_printf ("%s/async-implementation-success", name); + g_test_add (test_name, Test, fixture, setup, func, teardown); + g_free (test_name); + g_ptr_array_add (fixtures, fixture); + + /* Async implementation that fails */ + fixture = g_new0 (Fixture, 1); + fixture->ask_password_async_func = test_interaction_ask_password_async_failure; + fixture->ask_password_finish_func = test_interaction_ask_password_finish_failure; + fixture->ask_password_func = NULL; + fixture->result = G_TLS_INTERACTION_FAILED; + fixture->error_domain = G_FILE_ERROR; + fixture->error_code = G_FILE_ERROR_ACCES; + fixture->error_message = "The message"; + test_name = g_strdup_printf ("%s/async-implementation-failure", name); + g_test_add (test_name, Test, fixture, setup, func, teardown); + g_free (test_name); + g_ptr_array_add (fixtures, fixture); +} +static void +test_with_unhandled_ask_password_implementations (const gchar *name, + TestFunc setup, + TestFunc func, + TestFunc teardown, + GPtrArray *fixtures) +{ + gchar *test_name; + Fixture *fixture; + + /* Unhandled implementation */ + fixture = g_new0 (Fixture, 1); + fixture->ask_password_async_func = NULL; + fixture->ask_password_finish_func = NULL; + fixture->ask_password_func = NULL; + fixture->result = G_TLS_INTERACTION_UNHANDLED; + test_name = g_strdup_printf ("%s/unhandled-implementation", name); + g_test_add (test_name, Test, fixture, setup, func, teardown); + g_free (test_name); + g_ptr_array_add (fixtures, fixture); +} + +static void +test_with_sync_ask_password_implementations (const gchar *name, + TestFunc setup, + TestFunc func, + TestFunc teardown, + GPtrArray *fixtures) +{ + gchar *test_name; + Fixture *fixture; + + /* Sync implementation that succeeds */ + fixture = g_new0 (Fixture, 1); + fixture->ask_password_async_func = NULL; + fixture->ask_password_finish_func = NULL; + fixture->ask_password_func = test_interaction_ask_password_sync_success; + fixture->result = G_TLS_INTERACTION_HANDLED; + test_name = g_strdup_printf ("%s/sync-implementation-success", name); + g_test_add (test_name, Test, fixture, setup, func, teardown); + g_free (test_name); + g_ptr_array_add (fixtures, fixture); + + /* Async implementation that fails */ + fixture = g_new0 (Fixture, 1); + fixture->ask_password_async_func = NULL; + fixture->ask_password_finish_func = NULL; + fixture->ask_password_func = test_interaction_ask_password_sync_failure; + fixture->result = G_TLS_INTERACTION_FAILED; + fixture->error_domain = G_FILE_ERROR; + fixture->error_code = G_FILE_ERROR_ACCES; + fixture->error_message = "The message"; + test_name = g_strdup_printf ("%s/sync-implementation-failure", name); + g_test_add (test_name, Test, fixture, setup, func, teardown); + g_free (test_name); + g_ptr_array_add (fixtures, fixture); +} + +int +main (int argc, + char *argv[]) +{ + GPtrArray *fixtures; + gint ret; + + g_type_init (); + g_thread_init (NULL); + g_test_init (&argc, &argv, NULL); + + fixtures = g_ptr_array_new_with_free_func (g_free); + + /* Tests for g_tls_interaction_invoke_ask_password */ + + test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/invoke-with-loop", + setup_with_thread_loop, test_invoke_ask_password, + teardown_with_thread_loop, fixtures); + test_with_async_ask_password_implementations ("/tls-interaction/ask-password/invoke-with-loop", + setup_with_thread_loop, test_invoke_ask_password, + teardown_with_thread_loop, fixtures); + test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/invoke-with-loop", + setup_with_thread_loop, test_invoke_ask_password, + teardown_with_thread_loop, fixtures); + + test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/invoke-without-loop", + setup_without_loop, test_invoke_ask_password, + teardown_without_loop, fixtures); + test_with_async_ask_password_implementations ("/tls-interaction/ask-password/invoke-without-loop", + setup_without_loop, test_invoke_ask_password, + teardown_without_loop, fixtures); + test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/invoke-without-loop", + setup_without_loop, test_invoke_ask_password, + teardown_without_loop, fixtures); + + test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/invoke-in-loop", + setup_with_normal_loop, test_invoke_ask_password, + teardown_with_normal_loop, fixtures); + test_with_async_ask_password_implementations ("/tls-interaction/ask-password/invoke-in-loop", + setup_with_normal_loop, test_invoke_ask_password, + teardown_with_normal_loop, fixtures); + test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/invoke-in-loop", + setup_with_normal_loop, test_invoke_ask_password, + teardown_with_normal_loop, fixtures); + + /* Tests for g_tls_interaction_ask_password */ + test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/sync", + setup_without_loop, test_ask_password, + teardown_without_loop, fixtures); + test_with_sync_ask_password_implementations ("/tls-interaction/ask-password/sync", + setup_without_loop, test_ask_password, + teardown_without_loop, fixtures); + + /* Tests for g_tls_interaction_ask_password_async */ + test_with_unhandled_ask_password_implementations ("/tls-interaction/ask-password/async", + setup_with_normal_loop, test_ask_password_async, + teardown_with_normal_loop, fixtures); + test_with_async_ask_password_implementations ("/tls-interaction/ask-password/async", + setup_with_normal_loop, test_ask_password_async, + teardown_with_normal_loop, fixtures); + + ret = g_test_run(); + g_ptr_array_free (fixtures, TRUE); + return ret; +} -- cgit v1.2.1