Branch data Line data Source code
1 : : /* GIO - GLib Input, Output and Streaming Library
2 : : *
3 : : * Copyright © 2022 Endless OS Foundation, LLC
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 : : * SPDX-License-Identifier: LGPL-2.1-or-later
21 : : */
22 : :
23 : : #include <gio/gio.h>
24 : : #include <gio/gunixfdlist.h>
25 : : #include <string.h>
26 : : #include <unistd.h>
27 : :
28 : : #include "gdbusprivate.h"
29 : : #include "gdbus-tests.h"
30 : :
31 : : static const GDBusArgInfo foo_get_fds_in_args =
32 : : {
33 : : -1,
34 : : "type",
35 : : "s",
36 : : NULL
37 : : };
38 : : static const GDBusArgInfo * const foo_get_fds_in_arg_pointers[] = {&foo_get_fds_in_args, NULL};
39 : :
40 : : static const GDBusArgInfo foo_get_fds_out_args =
41 : : {
42 : : -1,
43 : : "some_fd",
44 : : "h",
45 : : NULL
46 : : };
47 : : static const GDBusArgInfo * const foo_get_fds_out_arg_pointers[] = {&foo_get_fds_out_args, NULL};
48 : :
49 : : static const GDBusMethodInfo foo_method_info_wrong_return_type =
50 : : {
51 : : -1,
52 : : "WrongReturnType",
53 : : NULL, /* in args */
54 : : NULL, /* out args */
55 : : NULL /* annotations */
56 : : };
57 : : static const GDBusMethodInfo foo_method_info_close_before_returning =
58 : : {
59 : : -1,
60 : : "CloseBeforeReturning",
61 : : NULL, /* in args */
62 : : NULL, /* out args */
63 : : NULL /* annotations */
64 : : };
65 : : static const GDBusMethodInfo foo_method_info_get_fds =
66 : : {
67 : : -1,
68 : : "GetFDs",
69 : : (GDBusArgInfo **) foo_get_fds_in_arg_pointers,
70 : : (GDBusArgInfo **) foo_get_fds_out_arg_pointers,
71 : : NULL /* annotations */
72 : : };
73 : : static const GDBusMethodInfo foo_method_info_return_error =
74 : : {
75 : : -1,
76 : : "ReturnError",
77 : : NULL, /* in args */
78 : : NULL, /* out args */
79 : : NULL /* annotations */
80 : : };
81 : : static const GDBusMethodInfo * const foo_method_info_pointers[] = {
82 : : &foo_method_info_wrong_return_type,
83 : : &foo_method_info_close_before_returning,
84 : : &foo_method_info_get_fds,
85 : : &foo_method_info_return_error,
86 : : NULL
87 : : };
88 : :
89 : : static const GDBusPropertyInfo foo_property_info[] =
90 : : {
91 : : {
92 : : -1,
93 : : "InvalidType",
94 : : "s",
95 : : G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
96 : : NULL
97 : : },
98 : : {
99 : : -1,
100 : : "InvalidTypeNull",
101 : : "s",
102 : : G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
103 : : NULL
104 : : },
105 : : {
106 : : -1,
107 : : "InvalidValueType",
108 : : "s",
109 : : G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE,
110 : : NULL
111 : : },
112 : : };
113 : : static const GDBusPropertyInfo * const foo_property_info_pointers[] =
114 : : {
115 : : &foo_property_info[0],
116 : : &foo_property_info[1],
117 : : &foo_property_info[2],
118 : : NULL
119 : : };
120 : :
121 : : static const GDBusInterfaceInfo foo_interface_info =
122 : : {
123 : : -1,
124 : : "org.example.Foo",
125 : : (GDBusMethodInfo **) &foo_method_info_pointers,
126 : : NULL, /* signals */
127 : : (GDBusPropertyInfo **) &foo_property_info_pointers,
128 : : NULL, /* annotations */
129 : : };
130 : :
131 : : /* ---------------------------------------------------------------------------------------------------- */
132 : :
133 : : static void
134 : 19 : test_method_invocation_return_method_call (GDBusConnection *connection,
135 : : const gchar *sender,
136 : : const gchar *object_path,
137 : : const gchar *interface_name,
138 : : const gchar *method_name,
139 : : GVariant *parameters,
140 : : GDBusMethodInvocation *invocation,
141 : : gpointer user_data)
142 : : {
143 : 19 : gboolean no_reply = g_dbus_message_get_flags (g_dbus_method_invocation_get_message (invocation)) & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED;
144 : :
145 : 19 : if (g_str_equal (interface_name, DBUS_INTERFACE_PROPERTIES) &&
146 : 10 : g_str_equal (method_name, "Get"))
147 : 6 : {
148 : : const gchar *iface_name, *prop_name;
149 : :
150 : 6 : g_variant_get (parameters, "(&s&s)", &iface_name, &prop_name);
151 : 6 : g_assert_cmpstr (iface_name, ==, "org.example.Foo");
152 : :
153 : : /* Do different things depending on the property name. */
154 : 6 : if (g_str_equal (prop_name, "InvalidType"))
155 : : {
156 : 2 : if (!no_reply)
157 : 1 : g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
158 : : "Type of return value for property 'Get' call should be '(v)' but got '(s)'");
159 : 2 : g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "this type is invalid"));
160 : : }
161 : 4 : else if (g_str_equal (prop_name, "InvalidTypeNull"))
162 : : {
163 : 2 : if (!no_reply)
164 : 1 : g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
165 : : "Type of return value for property 'Get' call should be '(v)' but got '()'");
166 : 2 : g_dbus_method_invocation_return_value (invocation, NULL);
167 : : }
168 : 2 : else if (g_str_equal (prop_name, "InvalidValueType"))
169 : : {
170 : 2 : if (!no_reply)
171 : 1 : g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
172 : : "Value returned from property 'Get' call for 'InvalidValueType' should be 's' but is 'u'");
173 : 2 : g_dbus_method_invocation_return_value (invocation, g_variant_new ("(v)", g_variant_new_uint32 (123)));
174 : : }
175 : : else
176 : : {
177 : : g_assert_not_reached ();
178 : : }
179 : :
180 : 6 : g_test_assert_expected_messages ();
181 : : }
182 : 13 : else if (g_str_equal (interface_name, DBUS_INTERFACE_PROPERTIES) &&
183 : 4 : g_str_equal (method_name, "Set"))
184 : 2 : {
185 : : const gchar *iface_name, *prop_name;
186 : : GVariant *value;
187 : :
188 : 2 : g_variant_get (parameters, "(&s&sv)", &iface_name, &prop_name, &value);
189 : 2 : g_assert_cmpstr (iface_name, ==, "org.example.Foo");
190 : :
191 : 2 : if (g_str_equal (prop_name, "InvalidType"))
192 : : {
193 : 2 : if (!no_reply)
194 : 1 : g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
195 : : "Type of return value for property 'Set' call should be '()' but got '(s)'");
196 : 2 : g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be unit"));
197 : : }
198 : : else
199 : : {
200 : : g_assert_not_reached ();
201 : : }
202 : :
203 : 2 : g_test_assert_expected_messages ();
204 : 2 : g_variant_unref (value);
205 : : }
206 : 11 : else if (g_str_equal (interface_name, DBUS_INTERFACE_PROPERTIES) &&
207 : 2 : g_str_equal (method_name, "GetAll"))
208 : 2 : {
209 : : const gchar *iface_name;
210 : :
211 : 2 : g_variant_get (parameters, "(&s)", &iface_name);
212 : 2 : g_assert_cmpstr (iface_name, ==, "org.example.Foo");
213 : :
214 : 2 : if (!no_reply)
215 : 1 : g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
216 : : "Type of return value for property 'GetAll' call should be '(a{sv})' but got '(s)'");
217 : 2 : g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be a different type"));
218 : : }
219 : 9 : else if (g_str_equal (interface_name, "org.example.Foo") &&
220 : 9 : g_str_equal (method_name, "WrongReturnType"))
221 : : {
222 : 2 : if (!no_reply)
223 : 1 : g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING,
224 : : "Type of return value is incorrect: expected '()', got '(s)'");
225 : 2 : g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be a different type"));
226 : : }
227 : 7 : else if (g_str_equal (interface_name, "org.example.Foo") &&
228 : 7 : g_str_equal (method_name, "CloseBeforeReturning"))
229 : : {
230 : 1 : g_dbus_connection_close (connection, NULL, NULL, NULL);
231 : :
232 : 1 : g_dbus_method_invocation_return_value (invocation, NULL);
233 : : }
234 : 6 : else if (g_str_equal (interface_name, "org.example.Foo") &&
235 : 6 : g_str_equal (method_name, "GetFDs"))
236 : 4 : {
237 : : const gchar *action;
238 : 4 : GUnixFDList *list = NULL;
239 : 4 : GError *local_error = NULL;
240 : :
241 : 4 : g_variant_get (parameters, "(&s)", &action);
242 : :
243 : 4 : list = g_unix_fd_list_new ();
244 : 4 : g_unix_fd_list_append (list, 1, &local_error);
245 : 4 : g_assert_no_error (local_error);
246 : :
247 : 4 : if (g_str_equal (action, "WrongNumber"))
248 : : {
249 : 2 : g_unix_fd_list_append (list, 1, &local_error);
250 : 2 : g_assert_no_error (local_error);
251 : : }
252 : :
253 : 4 : if (g_str_equal (action, "Valid") ||
254 : 2 : g_str_equal (action, "WrongNumber"))
255 : 4 : g_dbus_method_invocation_return_value_with_unix_fd_list (invocation, g_variant_new ("(h)", 0), list);
256 : : else
257 : : g_assert_not_reached ();
258 : :
259 : 4 : g_object_unref (list);
260 : : }
261 : 2 : else if (g_str_equal (interface_name, "org.example.Foo") &&
262 : 2 : g_str_equal (method_name, "ReturnError"))
263 : : {
264 : 2 : g_dbus_method_invocation_return_dbus_error (invocation, "org.example.Foo", "SomeError");
265 : : }
266 : : else
267 : : g_assert_not_reached ();
268 : 19 : }
269 : :
270 : : static void
271 : 10 : ensure_result_cb (GObject *source,
272 : : GAsyncResult *result,
273 : : gpointer user_data)
274 : : {
275 : 10 : GDBusConnection *connection = G_DBUS_CONNECTION (source);
276 : : GVariant *reply;
277 : 10 : guint *n_outstanding_calls = user_data;
278 : :
279 : 10 : reply = g_dbus_connection_call_finish (connection, result, NULL);
280 : :
281 : : /* We don’t care what the reply is. */
282 : 10 : g_clear_pointer (&reply, g_variant_unref);
283 : :
284 : 10 : g_assert_cmpint (*n_outstanding_calls, >, 0);
285 : 10 : *n_outstanding_calls = *n_outstanding_calls - 1;
286 : 10 : }
287 : :
288 : : static void
289 : 1 : test_method_invocation_return (void)
290 : : {
291 : 1 : GDBusConnection *connection = NULL;
292 : 1 : GError *local_error = NULL;
293 : : guint registration_id;
294 : 1 : const GDBusInterfaceVTable vtable = {
295 : : test_method_invocation_return_method_call, NULL, NULL, { 0 }
296 : : };
297 : 1 : guint n_outstanding_calls = 0;
298 : :
299 : 1 : g_test_summary ("Test calling g_dbus_method_invocation_return_*() in various ways");
300 : :
301 : : /* Connect to the bus. */
302 : 1 : connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error);
303 : 1 : g_assert_no_error (local_error);
304 : 1 : g_assert_nonnull (connection);
305 : :
306 : : /* Don’t exit the test when the server closes the connection in
307 : : * CloseBeforeReturning(). */
308 : 1 : g_dbus_connection_set_exit_on_close (connection, FALSE);
309 : :
310 : : /* Register an object which we can call methods on. */
311 : 1 : registration_id = g_dbus_connection_register_object (connection,
312 : : "/foo",
313 : : (GDBusInterfaceInfo *) &foo_interface_info,
314 : : &vtable, NULL, NULL, &local_error);
315 : 1 : g_assert_no_error (local_error);
316 : 1 : g_assert_cmpint (registration_id, !=, 0);
317 : :
318 : : /* Test a variety of error cases */
319 : : {
320 : : const struct
321 : : {
322 : : const gchar *interface_name;
323 : : const gchar *method_name;
324 : : const gchar *parameters_string;
325 : : gboolean tests_undefined_behaviour;
326 : : }
327 : 1 : calls[] =
328 : : {
329 : : { DBUS_INTERFACE_PROPERTIES, "Get", "('org.example.Foo', 'InvalidType')", TRUE },
330 : : { DBUS_INTERFACE_PROPERTIES, "Get", "('org.example.Foo', 'InvalidTypeNull')", TRUE },
331 : : { DBUS_INTERFACE_PROPERTIES, "Get", "('org.example.Foo', 'InvalidValueType')", TRUE },
332 : : { DBUS_INTERFACE_PROPERTIES, "Set", "('org.example.Foo', 'InvalidType', <'irrelevant'>)", TRUE },
333 : : { DBUS_INTERFACE_PROPERTIES, "GetAll", "('org.example.Foo',)", TRUE },
334 : : { "org.example.Foo", "WrongReturnType", "()", TRUE },
335 : : { "org.example.Foo", "GetFDs", "('Valid',)", FALSE },
336 : : { "org.example.Foo", "GetFDs", "('WrongNumber',)", TRUE },
337 : : { "org.example.Foo", "ReturnError", "()", FALSE },
338 : : { "org.example.Foo", "CloseBeforeReturning", "()", FALSE },
339 : : };
340 : : gsize i;
341 : :
342 : 11 : for (i = 0; i < G_N_ELEMENTS (calls); i++)
343 : : {
344 : 10 : if (calls[i].tests_undefined_behaviour && !g_test_undefined ())
345 : : {
346 : 0 : g_test_message ("Skipping %s.%s", calls[i].interface_name, calls[i].method_name);
347 : 0 : continue;
348 : : }
349 : : else
350 : : {
351 : 10 : g_test_message ("Calling %s.%s", calls[i].interface_name, calls[i].method_name);
352 : : }
353 : :
354 : : /* Call twice, once expecting a result and once not. Do the call which
355 : : * doesn’t expect a result first; message ordering should ensure that
356 : : * it’s completed by the time the second call completes, so we don’t
357 : : * have to account for it separately.
358 : : *
359 : : * That’s good, because the only way to get g_dbus_connection_call()
360 : : * to set %G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED is to not provide
361 : : * a callback function. */
362 : 10 : n_outstanding_calls++;
363 : :
364 : 10 : g_dbus_connection_call (connection,
365 : : g_dbus_connection_get_unique_name (connection),
366 : : "/foo",
367 : 10 : calls[i].interface_name,
368 : 10 : calls[i].method_name,
369 : 10 : g_variant_new_parsed (calls[i].parameters_string),
370 : : NULL,
371 : : G_DBUS_CALL_FLAGS_NONE,
372 : : -1,
373 : : NULL,
374 : : NULL, /* no callback */
375 : : NULL);
376 : :
377 : 10 : g_dbus_connection_call (connection,
378 : : g_dbus_connection_get_unique_name (connection),
379 : : "/foo",
380 : 10 : calls[i].interface_name,
381 : 10 : calls[i].method_name,
382 : 10 : g_variant_new_parsed (calls[i].parameters_string),
383 : : NULL,
384 : : G_DBUS_CALL_FLAGS_NONE,
385 : : -1,
386 : : NULL,
387 : : ensure_result_cb,
388 : : &n_outstanding_calls);
389 : : }
390 : : }
391 : :
392 : : /* Wait until all the calls are complete. */
393 : 19 : while (n_outstanding_calls > 0)
394 : 18 : g_main_context_iteration (NULL, TRUE);
395 : :
396 : 1 : g_dbus_connection_unregister_object (connection, registration_id);
397 : 1 : g_object_unref (connection);
398 : 1 : }
399 : :
400 : : int
401 : 1 : main (int argc,
402 : : char *argv[])
403 : : {
404 : 1 : g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL);
405 : :
406 : 1 : g_test_add_func ("/gdbus/method-invocation/return", test_method_invocation_return);
407 : :
408 : 1 : return session_bus_run ();
409 : : }
|