1 /* GIO - GLib Input, Output and Streaming Library
2 *
3 * Copyright (C) 2018 Igalia S.L.
4 *
5 * SPDX-License-Identifier: LGPL-2.1-or-later
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General
18 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "mock-resolver.h"
22
23 struct _MockResolver
24 {
25 GResolver parent_instance;
26 guint ipv4_delay_ms;
27 guint ipv6_delay_ms;
28 GList *ipv4_results;
29 GList *ipv6_results;
30 GError *ipv4_error;
31 GError *ipv6_error;
32 };
33
34 G_DEFINE_TYPE (MockResolver, mock_resolver, G_TYPE_RESOLVER)
35
36 MockResolver *
37 mock_resolver_new (void)
38 {
39 return g_object_new (MOCK_TYPE_RESOLVER, NULL);
40 }
41
42 void
43 mock_resolver_set_ipv4_delay_ms (MockResolver *self, guint delay_ms)
44 {
45 self->ipv4_delay_ms = delay_ms;
46 }
47
48 static gpointer
49 copy_object (gconstpointer obj, gpointer user_data)
50 {
51 return g_object_ref (G_OBJECT (obj));
52 }
53
54 void
55 mock_resolver_set_ipv4_results (MockResolver *self, GList *results)
56 {
57 if (self->ipv4_results)
58 g_list_free_full (self->ipv4_results, g_object_unref);
59 self->ipv4_results = g_list_copy_deep (results, copy_object, NULL);
60 }
61
62 void
63 mock_resolver_set_ipv4_error (MockResolver *self, GError *error)
64 {
65 g_clear_error (&self->ipv4_error);
66 if (error)
67 self->ipv4_error = g_error_copy (error);
68 }
69
70 void
71 mock_resolver_set_ipv6_delay_ms (MockResolver *self, guint delay_ms)
72 {
73 self->ipv6_delay_ms = delay_ms;
74 }
75
76 void
77 mock_resolver_set_ipv6_results (MockResolver *self, GList *results)
78 {
79 if (self->ipv6_results)
80 g_list_free_full (self->ipv6_results, g_object_unref);
81 self->ipv6_results = g_list_copy_deep (results, copy_object, NULL);
82 }
83
84 void
85 mock_resolver_set_ipv6_error (MockResolver *self, GError *error)
86 {
87 g_clear_error (&self->ipv6_error);
88 if (error)
89 self->ipv6_error = g_error_copy (error);
90 }
91
92 static gboolean lookup_by_name_cb (gpointer user_data);
93
94 /* Core of the implementation of `lookup_by_name()` in the mock resolver.
95 *
96 * It creates a #GSource which will become ready with the resolver results. It
97 * will become ready either after a timeout, or as an idle callback. This
98 * simulates doing some actual network-based resolution work.
99 *
100 * A previous implementation of this did the work in a thread, but that made it
101 * hard to synchronise the timeouts with the #GResolver failure timeouts in the
102 * calling thread, as spawning a worker thread could be subject to non-trivial
103 * delays. */
104 static void
105 do_lookup_by_name (MockResolver *self,
106 GTask *task,
107 GResolverNameLookupFlags flags)
108 {
109 GSource *source = NULL;
110
111 g_task_set_task_data (task, GINT_TO_POINTER (flags), NULL);
112
113 if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY)
114 source = g_timeout_source_new (self->ipv4_delay_ms);
115 else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)
116 source = g_timeout_source_new (self->ipv6_delay_ms);
117 else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT)
118 source = g_idle_source_new ();
119 else
120 g_assert_not_reached ();
121
122 g_source_set_callback (source, lookup_by_name_cb, g_object_ref (task), g_object_unref);
123 g_source_attach (source, g_main_context_get_thread_default ());
124 g_source_unref (source);
125 }
126
127 static gboolean
128 lookup_by_name_cb (gpointer user_data)
129 {
130 GTask *task = G_TASK (user_data);
131 MockResolver *self = g_task_get_source_object (task);
132 GResolverNameLookupFlags flags = GPOINTER_TO_INT (g_task_get_task_data (task));
133
134 if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY)
135 {
136 if (self->ipv4_error)
137 g_task_return_error (task, g_error_copy (self->ipv4_error));
138 else
139 g_task_return_pointer (task, g_list_copy_deep (self->ipv4_results, copy_object, NULL), NULL);
140 }
141 else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)
142 {
143 if (self->ipv6_error)
144 g_task_return_error (task, g_error_copy (self->ipv6_error));
145 else
146 g_task_return_pointer (task, g_list_copy_deep (self->ipv6_results, copy_object, NULL), NULL);
147 }
148 else if (flags == G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT)
149 {
150 /* This is only the minimal implementation needed for some tests */
151 g_assert (self->ipv4_error == NULL && self->ipv6_error == NULL && self->ipv6_results == NULL);
152 g_task_return_pointer (task, g_list_copy_deep (self->ipv4_results, copy_object, NULL), NULL);
153 }
154 else
155 g_assert_not_reached ();
156
157 return G_SOURCE_REMOVE;
158 }
159
160 static void
161 lookup_by_name_with_flags_async (GResolver *resolver,
162 const gchar *hostname,
163 GResolverNameLookupFlags flags,
164 GCancellable *cancellable,
165 GAsyncReadyCallback callback,
166 gpointer user_data)
167 {
168 MockResolver *self = MOCK_RESOLVER (resolver);
169 GTask *task = NULL;
170
171 task = g_task_new (resolver, cancellable, callback, user_data);
172 g_task_set_source_tag (task, lookup_by_name_with_flags_async);
173
174 do_lookup_by_name (self, task, flags);
175
176 g_object_unref (task);
177 }
178
179 static void
180 async_result_cb (GObject *source_object,
181 GAsyncResult *result,
182 gpointer user_data)
183 {
184 GAsyncResult **result_out = user_data;
185
186 g_assert (*result_out == NULL);
187 *result_out = g_object_ref (result);
188
189 g_main_context_wakeup (g_main_context_get_thread_default ());
190 }
191
192 static GList *
193 lookup_by_name (GResolver *resolver,
194 const gchar *hostname,
195 GCancellable *cancellable,
196 GError **error)
197 {
198 MockResolver *self = MOCK_RESOLVER (resolver);
199 GMainContext *context = NULL;
200 GList *result = NULL;
201 GAsyncResult *async_result = NULL;
202 GTask *task = NULL;
203
204 context = g_main_context_new ();
205 g_main_context_push_thread_default (context);
206
207 task = g_task_new (resolver, cancellable, async_result_cb, &async_result);
208 g_task_set_source_tag (task, lookup_by_name);
209
210 /* Set up the resolution job. */
211 do_lookup_by_name (self, task, G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT);
212
213 /* Wait for it to complete synchronously. */
214 while (async_result == NULL)
215 g_main_context_iteration (context, TRUE);
216
217 result = g_task_propagate_pointer (G_TASK (async_result), error);
218 g_object_unref (async_result);
219
220 g_assert_finalize_object (task);
221
222 g_main_context_pop_thread_default (context);
223 g_main_context_unref (context);
224
225 return g_steal_pointer (&result);
226 }
227
228 static GList *
229 lookup_by_name_with_flags_finish (GResolver *resolver,
230 GAsyncResult *result,
231 GError **error)
232 {
233 return g_task_propagate_pointer (G_TASK (result), error);
234 }
235
236 static void
237 mock_resolver_finalize (GObject *object)
238 {
239 MockResolver *self = (MockResolver*)object;
240
241 g_clear_error (&self->ipv4_error);
242 g_clear_error (&self->ipv6_error);
243 if (self->ipv6_results)
244 g_list_free_full (self->ipv6_results, g_object_unref);
245 if (self->ipv4_results)
246 g_list_free_full (self->ipv4_results, g_object_unref);
247
248 G_OBJECT_CLASS (mock_resolver_parent_class)->finalize (object);
249 }
250
251 static void
252 mock_resolver_class_init (MockResolverClass *klass)
253 {
254 GResolverClass *resolver_class = G_RESOLVER_CLASS (klass);
255 GObjectClass *object_class = G_OBJECT_CLASS (klass);
256 resolver_class->lookup_by_name_with_flags_async = lookup_by_name_with_flags_async;
257 resolver_class->lookup_by_name_with_flags_finish = lookup_by_name_with_flags_finish;
258 resolver_class->lookup_by_name = lookup_by_name;
259 object_class->finalize = mock_resolver_finalize;
260 }
261
262 static void
263 mock_resolver_init (MockResolver *self)
264 {
265 }