| /* GIO - GLib Input, Output and Streaming Library |
| * |
| * Copyright © 2022 Endless OS Foundation, LLC |
| * |
| * 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 <http://www.gnu.org/licenses/>. |
| * |
| * SPDX-License-Identifier: LGPL-2.1-or-later |
| */ |
| |
| #include <gio/gio.h> |
| #include <gio/gunixfdlist.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include "gdbus-tests.h" |
| |
| static const GDBusArgInfo foo_get_fds_in_args = |
| { |
| -1, |
| "type", |
| "s", |
| NULL |
| }; |
| static const GDBusArgInfo * const foo_get_fds_in_arg_pointers[] = {&foo_get_fds_in_args, NULL}; |
| |
| static const GDBusArgInfo foo_get_fds_out_args = |
| { |
| -1, |
| "some_fd", |
| "h", |
| NULL |
| }; |
| static const GDBusArgInfo * const foo_get_fds_out_arg_pointers[] = {&foo_get_fds_out_args, NULL}; |
| |
| static const GDBusMethodInfo foo_method_info_wrong_return_type = |
| { |
| -1, |
| "WrongReturnType", |
| NULL, /* in args */ |
| NULL, /* out args */ |
| NULL /* annotations */ |
| }; |
| static const GDBusMethodInfo foo_method_info_close_before_returning = |
| { |
| -1, |
| "CloseBeforeReturning", |
| NULL, /* in args */ |
| NULL, /* out args */ |
| NULL /* annotations */ |
| }; |
| static const GDBusMethodInfo foo_method_info_get_fds = |
| { |
| -1, |
| "GetFDs", |
| (GDBusArgInfo **) foo_get_fds_in_arg_pointers, |
| (GDBusArgInfo **) foo_get_fds_out_arg_pointers, |
| NULL /* annotations */ |
| }; |
| static const GDBusMethodInfo foo_method_info_return_error = |
| { |
| -1, |
| "ReturnError", |
| NULL, /* in args */ |
| NULL, /* out args */ |
| NULL /* annotations */ |
| }; |
| static const GDBusMethodInfo * const foo_method_info_pointers[] = { |
| &foo_method_info_wrong_return_type, |
| &foo_method_info_close_before_returning, |
| &foo_method_info_get_fds, |
| &foo_method_info_return_error, |
| NULL |
| }; |
| |
| static const GDBusPropertyInfo foo_property_info[] = |
| { |
| { |
| -1, |
| "InvalidType", |
| "s", |
| G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, |
| NULL |
| }, |
| { |
| -1, |
| "InvalidTypeNull", |
| "s", |
| G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, |
| NULL |
| }, |
| { |
| -1, |
| "InvalidValueType", |
| "s", |
| G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE, |
| NULL |
| }, |
| }; |
| static const GDBusPropertyInfo * const foo_property_info_pointers[] = |
| { |
| &foo_property_info[0], |
| &foo_property_info[1], |
| &foo_property_info[2], |
| NULL |
| }; |
| |
| static const GDBusInterfaceInfo foo_interface_info = |
| { |
| -1, |
| "org.example.Foo", |
| (GDBusMethodInfo **) &foo_method_info_pointers, |
| NULL, /* signals */ |
| (GDBusPropertyInfo **) &foo_property_info_pointers, |
| NULL, /* annotations */ |
| }; |
| |
| /* ---------------------------------------------------------------------------------------------------- */ |
| |
| static void |
| test_method_invocation_return_method_call (GDBusConnection *connection, |
| const gchar *sender, |
| const gchar *object_path, |
| const gchar *interface_name, |
| const gchar *method_name, |
| GVariant *parameters, |
| GDBusMethodInvocation *invocation, |
| gpointer user_data) |
| { |
| gboolean no_reply = g_dbus_message_get_flags (g_dbus_method_invocation_get_message (invocation)) & G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED; |
| |
| if (g_str_equal (interface_name, "org.freedesktop.DBus.Properties") && |
| g_str_equal (method_name, "Get")) |
| { |
| const gchar *iface_name, *prop_name; |
| |
| g_variant_get (parameters, "(&s&s)", &iface_name, &prop_name); |
| g_assert_cmpstr (iface_name, ==, "org.example.Foo"); |
| |
| /* Do different things depending on the property name. */ |
| if (g_str_equal (prop_name, "InvalidType")) |
| { |
| if (!no_reply) |
| g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, |
| "Type of return value for property 'Get' call should be '(v)' but got '(s)'"); |
| g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "this type is invalid")); |
| } |
| else if (g_str_equal (prop_name, "InvalidTypeNull")) |
| { |
| if (!no_reply) |
| g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, |
| "Type of return value for property 'Get' call should be '(v)' but got '()'"); |
| g_dbus_method_invocation_return_value (invocation, NULL); |
| } |
| else if (g_str_equal (prop_name, "InvalidValueType")) |
| { |
| if (!no_reply) |
| g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, |
| "Value returned from property 'Get' call for 'InvalidValueType' should be 's' but is 'u'"); |
| g_dbus_method_invocation_return_value (invocation, g_variant_new ("(v)", g_variant_new_uint32 (123))); |
| } |
| else |
| { |
| g_assert_not_reached (); |
| } |
| |
| g_test_assert_expected_messages (); |
| } |
| else if (g_str_equal (interface_name, "org.freedesktop.DBus.Properties") && |
| g_str_equal (method_name, "Set")) |
| { |
| const gchar *iface_name, *prop_name; |
| GVariant *value; |
| |
| g_variant_get (parameters, "(&s&sv)", &iface_name, &prop_name, &value); |
| g_assert_cmpstr (iface_name, ==, "org.example.Foo"); |
| |
| if (g_str_equal (prop_name, "InvalidType")) |
| { |
| if (!no_reply) |
| g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, |
| "Type of return value for property 'Set' call should be '()' but got '(s)'"); |
| g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be unit")); |
| } |
| else |
| { |
| g_assert_not_reached (); |
| } |
| |
| g_test_assert_expected_messages (); |
| g_variant_unref (value); |
| } |
| else if (g_str_equal (interface_name, "org.freedesktop.DBus.Properties") && |
| g_str_equal (method_name, "GetAll")) |
| { |
| const gchar *iface_name; |
| |
| g_variant_get (parameters, "(&s)", &iface_name); |
| g_assert_cmpstr (iface_name, ==, "org.example.Foo"); |
| |
| if (!no_reply) |
| g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, |
| "Type of return value for property 'GetAll' call should be '(a{sv})' but got '(s)'"); |
| g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be a different type")); |
| } |
| else if (g_str_equal (interface_name, "org.example.Foo") && |
| g_str_equal (method_name, "WrongReturnType")) |
| { |
| if (!no_reply) |
| g_test_expect_message ("GLib-GIO", G_LOG_LEVEL_WARNING, |
| "Type of return value is incorrect: expected '()', got '(s)'"); |
| g_dbus_method_invocation_return_value (invocation, g_variant_new ("(s)", "should be a different type")); |
| } |
| else if (g_str_equal (interface_name, "org.example.Foo") && |
| g_str_equal (method_name, "CloseBeforeReturning")) |
| { |
| g_dbus_connection_close (connection, NULL, NULL, NULL); |
| |
| g_dbus_method_invocation_return_value (invocation, NULL); |
| } |
| else if (g_str_equal (interface_name, "org.example.Foo") && |
| g_str_equal (method_name, "GetFDs")) |
| { |
| const gchar *action; |
| GUnixFDList *list = NULL; |
| GError *local_error = NULL; |
| |
| g_variant_get (parameters, "(&s)", &action); |
| |
| list = g_unix_fd_list_new (); |
| g_unix_fd_list_append (list, 1, &local_error); |
| g_assert_no_error (local_error); |
| |
| if (g_str_equal (action, "WrongNumber")) |
| { |
| g_unix_fd_list_append (list, 1, &local_error); |
| g_assert_no_error (local_error); |
| } |
| |
| if (g_str_equal (action, "Valid") || |
| g_str_equal (action, "WrongNumber")) |
| g_dbus_method_invocation_return_value_with_unix_fd_list (invocation, g_variant_new ("(h)"), list); |
| else |
| g_assert_not_reached (); |
| |
| g_object_unref (list); |
| } |
| else if (g_str_equal (interface_name, "org.example.Foo") && |
| g_str_equal (method_name, "ReturnError")) |
| { |
| g_dbus_method_invocation_return_dbus_error (invocation, "org.example.Foo", "SomeError"); |
| } |
| else |
| g_assert_not_reached (); |
| } |
| |
| static void |
| ensure_result_cb (GObject *source, |
| GAsyncResult *result, |
| gpointer user_data) |
| { |
| GDBusConnection *connection = G_DBUS_CONNECTION (source); |
| GVariant *reply; |
| guint *n_outstanding_calls = user_data; |
| |
| reply = g_dbus_connection_call_finish (connection, result, NULL); |
| |
| /* We don’t care what the reply is. */ |
| g_clear_pointer (&reply, g_variant_unref); |
| |
| g_assert_cmpint (*n_outstanding_calls, >, 0); |
| *n_outstanding_calls = *n_outstanding_calls - 1; |
| } |
| |
| static void |
| test_method_invocation_return (void) |
| { |
| GDBusConnection *connection = NULL; |
| GError *local_error = NULL; |
| guint registration_id; |
| const GDBusInterfaceVTable vtable = { |
| test_method_invocation_return_method_call, NULL, NULL, { 0 } |
| }; |
| guint n_outstanding_calls = 0; |
| |
| g_test_summary ("Test calling g_dbus_method_invocation_return_*() in various ways"); |
| |
| /* Connect to the bus. */ |
| connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &local_error); |
| g_assert_no_error (local_error); |
| g_assert_nonnull (connection); |
| |
| /* Register an object which we can call methods on. */ |
| registration_id = g_dbus_connection_register_object (connection, |
| "/foo", |
| (GDBusInterfaceInfo *) &foo_interface_info, |
| &vtable, NULL, NULL, &local_error); |
| g_assert_no_error (local_error); |
| g_assert_cmpint (registration_id, !=, 0); |
| |
| /* Test a variety of error cases */ |
| { |
| const struct |
| { |
| const gchar *interface_name; |
| const gchar *method_name; |
| const gchar *parameters_string; |
| gboolean tests_undefined_behaviour; |
| } |
| calls[] = |
| { |
| { "org.freedesktop.DBus.Properties", "Get", "('org.example.Foo', 'InvalidType')", TRUE }, |
| { "org.freedesktop.DBus.Properties", "Get", "('org.example.Foo', 'InvalidTypeNull')", TRUE }, |
| { "org.freedesktop.DBus.Properties", "Get", "('org.example.Foo', 'InvalidValueType')", TRUE }, |
| { "org.freedesktop.DBus.Properties", "Set", "('org.example.Foo', 'InvalidType', <'irrelevant'>)", TRUE }, |
| { "org.freedesktop.DBus.Properties", "GetAll", "('org.example.Foo',)", TRUE }, |
| { "org.example.Foo", "WrongReturnType", "()", TRUE }, |
| { "org.example.Foo", "GetFDs", "('Valid',)", FALSE }, |
| { "org.example.Foo", "GetFDs", "('WrongNumber',)", TRUE }, |
| { "org.example.Foo", "ReturnError", "()", FALSE }, |
| { "org.example.Foo", "CloseBeforeReturning", "()", FALSE }, |
| }; |
| gsize i; |
| |
| for (i = 0; i < G_N_ELEMENTS (calls); i++) |
| { |
| if (calls[i].tests_undefined_behaviour && !g_test_undefined ()) |
| { |
| g_test_message ("Skipping %s.%s", calls[i].interface_name, calls[i].method_name); |
| continue; |
| } |
| else |
| { |
| g_test_message ("Calling %s.%s", calls[i].interface_name, calls[i].method_name); |
| } |
| |
| /* Call twice, once expecting a result and once not. Do the call which |
| * doesn’t expect a result first; message ordering should ensure that |
| * it’s completed by the time the second call completes, so we don’t |
| * have to account for it separately. |
| * |
| * That’s good, because the only way to get g_dbus_connection_call() |
| * to set %G_DBUS_MESSAGE_FLAGS_NO_REPLY_EXPECTED is to not provide |
| * a callback function. */ |
| n_outstanding_calls++; |
| |
| g_dbus_connection_call (connection, |
| g_dbus_connection_get_unique_name (connection), |
| "/foo", |
| calls[i].interface_name, |
| calls[i].method_name, |
| g_variant_new_parsed (calls[i].parameters_string), |
| NULL, |
| G_DBUS_CALL_FLAGS_NONE, |
| -1, |
| NULL, |
| NULL, /* no callback */ |
| NULL); |
| |
| g_dbus_connection_call (connection, |
| g_dbus_connection_get_unique_name (connection), |
| "/foo", |
| calls[i].interface_name, |
| calls[i].method_name, |
| g_variant_new_parsed (calls[i].parameters_string), |
| NULL, |
| G_DBUS_CALL_FLAGS_NONE, |
| -1, |
| NULL, |
| ensure_result_cb, |
| &n_outstanding_calls); |
| } |
| } |
| |
| /* Wait until all the calls are complete. */ |
| while (n_outstanding_calls > 0) |
| g_main_context_iteration (NULL, TRUE); |
| |
| g_dbus_connection_unregister_object (connection, registration_id); |
| g_object_unref (connection); |
| } |
| |
| int |
| main (int argc, |
| char *argv[]) |
| { |
| g_test_init (&argc, &argv, G_TEST_OPTION_ISOLATE_DIRS, NULL); |
| |
| g_test_add_func ("/gdbus/method-invocation/return", test_method_invocation_return); |
| |
| return session_bus_run (); |
| } |