/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2018 Igalia S.L. * * SPDX-License-Identifier: LGPL-2.1-or-later * * 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.1 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, see . */ #include "mock-resolver.h" struct _MockResolver { GResolver parent_instance; guint ipv4_delay_ms; guint ipv6_delay_ms; GList *ipv4_results; GList *ipv6_results; GError *ipv4_error; GError *ipv6_error; }; G_DEFINE_TYPE (MockResolver, mock_resolver, G_TYPE_RESOLVER) MockResolver * mock_resolver_new (void) { return g_object_new (MOCK_TYPE_RESOLVER, NULL); } void mock_resolver_set_ipv4_delay_ms (MockResolver *self, guint delay_ms) { self->ipv4_delay_ms = delay_ms; } static gpointer copy_object (gconstpointer obj, gpointer user_data) { return g_object_ref (G_OBJECT (obj)); } void mock_resolver_set_ipv4_results (MockResolver *self, GList *results) { if (self->ipv4_results) g_list_free_full (self->ipv4_results, g_object_unref); self->ipv4_results = g_list_copy_deep (results, copy_object, NULL); } void mock_resolver_set_ipv4_error (MockResolver *self, GError *error) { g_clear_error (&self->ipv4_error); if (error) self->ipv4_error = g_error_copy (error); } void mock_resolver_set_ipv6_delay_ms (MockResolver *self, guint delay_ms) { self->ipv6_delay_ms = delay_ms; } void mock_resolver_set_ipv6_results (MockResolver *self, GList *results) { if (self->ipv6_results) g_list_free_full (self->ipv6_results, g_object_unref); self->ipv6_results = g_list_copy_deep (results, copy_object, NULL); } void mock_resolver_set_ipv6_error (MockResolver *self, GError *error) { g_clear_error (&self->ipv6_error); if (error) self->ipv6_error = g_error_copy (error); } static gboolean lookup_by_name_cb (gpointer user_data); /* Core of the implementation of `lookup_by_name()` in the mock resolver. * * It creates a #GSource which will become ready with the resolver results. It * will become ready either after a timeout, or as an idle callback. This * simulates doing some actual network-based resolution work. * * A previous implementation of this did the work in a thread, but that made it * hard to synchronise the timeouts with the #GResolver failure timeouts in the * calling thread, as spawning a worker thread could be subject to non-trivial * delays. */ static void do_lookup_by_name (MockResolver *self, GTask *task, GResolverNameLookupFlags flags) { GSource *source = NULL; g_task_set_task_data (task, GINT_TO_POINTER (flags), NULL); if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY) source = g_timeout_source_new (self->ipv4_delay_ms); else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY) source = g_timeout_source_new (self->ipv6_delay_ms); else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT) source = g_idle_source_new (); else g_assert_not_reached (); g_source_set_callback (source, lookup_by_name_cb, g_object_ref (task), g_object_unref); g_source_attach (source, g_main_context_get_thread_default ()); g_source_unref (source); } static gboolean lookup_by_name_cb (gpointer user_data) { GTask *task = G_TASK (user_data); MockResolver *self = g_task_get_source_object (task); GResolverNameLookupFlags flags = GPOINTER_TO_INT (g_task_get_task_data (task)); if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY) { if (self->ipv4_error) g_task_return_error (task, g_error_copy (self->ipv4_error)); else g_task_return_pointer (task, g_list_copy_deep (self->ipv4_results, copy_object, NULL), NULL); } else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY) { if (self->ipv6_error) g_task_return_error (task, g_error_copy (self->ipv6_error)); else g_task_return_pointer (task, g_list_copy_deep (self->ipv6_results, copy_object, NULL), NULL); } else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT) { /* This is only the minimal implementation needed for some tests */ g_assert (self->ipv4_error == NULL && self->ipv6_error == NULL && self->ipv6_results == NULL); g_task_return_pointer (task, g_list_copy_deep (self->ipv4_results, copy_object, NULL), NULL); } else g_assert_not_reached (); return G_SOURCE_REMOVE; } static void lookup_by_name_with_flags_async (GResolver *resolver, const gchar *hostname, GResolverNameLookupFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { MockResolver *self = MOCK_RESOLVER (resolver); GTask *task = NULL; task = g_task_new (resolver, cancellable, callback, user_data); g_task_set_source_tag (task, lookup_by_name_with_flags_async); do_lookup_by_name (self, task, flags); g_object_unref (task); } static void async_result_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { GAsyncResult **result_out = user_data; g_assert (*result_out == NULL); *result_out = g_object_ref (result); g_main_context_wakeup (g_main_context_get_thread_default ()); } static GList * lookup_by_name (GResolver *resolver, const gchar *hostname, GCancellable *cancellable, GError **error) { MockResolver *self = MOCK_RESOLVER (resolver); GMainContext *context = NULL; GList *result = NULL; GAsyncResult *async_result = NULL; GTask *task = NULL; context = g_main_context_new (); g_main_context_push_thread_default (context); task = g_task_new (resolver, cancellable, async_result_cb, &async_result); g_task_set_source_tag (task, lookup_by_name); /* Set up the resolution job. */ do_lookup_by_name (self, task, G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT); /* Wait for it to complete synchronously. */ while (async_result == NULL) g_main_context_iteration (context, TRUE); result = g_task_propagate_pointer (G_TASK (async_result), error); g_object_unref (async_result); g_assert_finalize_object (task); g_main_context_pop_thread_default (context); g_main_context_unref (context); return g_steal_pointer (&result); } static GList * lookup_by_name_with_flags_finish (GResolver *resolver, GAsyncResult *result, GError **error) { return g_task_propagate_pointer (G_TASK (result), error); } static void mock_resolver_finalize (GObject *object) { MockResolver *self = (MockResolver*)object; g_clear_error (&self->ipv4_error); g_clear_error (&self->ipv6_error); if (self->ipv6_results) g_list_free_full (self->ipv6_results, g_object_unref); if (self->ipv4_results) g_list_free_full (self->ipv4_results, g_object_unref); G_OBJECT_CLASS (mock_resolver_parent_class)->finalize (object); } static void mock_resolver_class_init (MockResolverClass *klass) { GResolverClass *resolver_class = G_RESOLVER_CLASS (klass); GObjectClass *object_class = G_OBJECT_CLASS (klass); resolver_class->lookup_by_name_with_flags_async = lookup_by_name_with_flags_async; resolver_class->lookup_by_name_with_flags_finish = lookup_by_name_with_flags_finish; resolver_class->lookup_by_name = lookup_by_name; object_class->finalize = mock_resolver_finalize; } static void mock_resolver_init (MockResolver *self) { }