// SPDX-License-Identifier: LGPL-2.1+
/* NetworkManager -- Network link manager
 *
 * (C) Copyright 2019 Red Hat, Inc.
 */

#include "nm-default.h"

#include "nm-dbus-aux.h"

/*****************************************************************************/

static void
_nm_dbus_connection_call_get_name_owner_cb (GObject *source,
                                            GAsyncResult *res,
                                            gpointer user_data)
{
	gs_unref_variant GVariant *ret = NULL;
	gs_free_error GError *error = NULL;
	const char *owner = NULL;
	gpointer orig_user_data;
	NMDBusConnectionCallGetNameOwnerCb callback;

	nm_utils_user_data_unpack (user_data, &orig_user_data, &callback);

	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);
	if (ret)
		g_variant_get (ret, "(&s)", &owner);

	callback (owner, error, orig_user_data);
}

void
nm_dbus_connection_call_get_name_owner (GDBusConnection *dbus_connection,
                                        const char *service_name,
                                        int timeout_msec,
                                        GCancellable *cancellable,
                                        NMDBusConnectionCallGetNameOwnerCb callback,
                                        gpointer user_data)
{
	nm_assert (callback);

	g_dbus_connection_call (dbus_connection,
	                        DBUS_SERVICE_DBUS,
	                        DBUS_PATH_DBUS,
	                        DBUS_INTERFACE_DBUS,
	                        "GetNameOwner",
	                        g_variant_new ("(s)", service_name),
	                        G_VARIANT_TYPE ("(s)"),
	                        G_DBUS_CALL_FLAGS_NONE,
	                        timeout_msec,
	                        cancellable,
	                        _nm_dbus_connection_call_get_name_owner_cb,
	                        nm_utils_user_data_pack (user_data, callback));
}

/*****************************************************************************/

static void
_nm_dbus_connection_call_get_all_cb (GObject *source,
                                     GAsyncResult *res,
                                     gpointer user_data)
{
	gs_unref_variant GVariant *ret = NULL;
	gs_free_error GError *error = NULL;
	gpointer orig_user_data;
	NMDBusConnectionCallDefaultCb callback;

	nm_utils_user_data_unpack (user_data, &orig_user_data, &callback);

	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);

	nm_assert ((!!ret) != (!!error));

	callback (ret, error, orig_user_data);
}

void
nm_dbus_connection_call_get_all (GDBusConnection *dbus_connection,
                                 const char *bus_name,
                                 const char *object_path,
                                 const char *interface_name,
                                 int timeout_msec,
                                 GCancellable *cancellable,
                                 NMDBusConnectionCallDefaultCb callback,
                                 gpointer user_data)
{
	nm_assert (callback);

	g_dbus_connection_call (dbus_connection,
	                        bus_name,
	                        object_path,
	                        DBUS_INTERFACE_PROPERTIES,
	                        "GetAll",
	                        g_variant_new ("(s)", interface_name),
	                        G_VARIANT_TYPE ("(a{sv})"),
	                        G_DBUS_CALL_FLAGS_NONE,
	                        timeout_msec,
	                        cancellable,
	                        _nm_dbus_connection_call_get_all_cb,
	                        nm_utils_user_data_pack (user_data, callback));
}

/*****************************************************************************/

typedef struct {
	NMDBusConnectionSignalObjectMangerCb callback;
	gpointer user_data;
	GDestroyNotify user_data_free_func;
} SubscribeObjectManagerData;

static void
_subscribe_object_manager_cb (GDBusConnection *connection,
                              const char *sender_name,
                              const char *arg_object_path,
                              const char *interface_name,
                              const char *signal_name,
                              GVariant *parameters,
                              gpointer user_data)
{
	const SubscribeObjectManagerData *d = user_data;

	nm_assert (nm_streq0 (interface_name, DBUS_INTERFACE_OBJECT_MANAGER));

	if (nm_streq (signal_name, "InterfacesAdded")) {
		gs_unref_variant GVariant *interfaces_and_properties = NULL;
		const char *object_path;

		if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oa{sa{sv}})")))
			return;

		g_variant_get (parameters,
		               "(&o@a{sa{sv}})",
		               &object_path,
		               &interfaces_and_properties);

		d->callback (object_path, interfaces_and_properties, NULL, d->user_data);
		return;
	}

	if (nm_streq (signal_name, "InterfacesRemoved")) {
		gs_free const char **interfaces = NULL;
		const char *object_path;

		if (!g_variant_is_of_type (parameters, G_VARIANT_TYPE ("(oas)")))
			return;

		g_variant_get (parameters,
		               "(&o^a&s)",
		               &object_path,
		               &interfaces);

		d->callback (object_path, NULL, interfaces, d->user_data);
		return;
	}
}

static void
_subscribe_object_manager_data_free (gpointer ptr)
{
	SubscribeObjectManagerData *d = ptr;

	if (d->user_data_free_func)
		d->user_data_free_func (d->user_data);
	nm_g_slice_free (d);
}

guint
nm_dbus_connection_signal_subscribe_object_manager (GDBusConnection *dbus_connection,
                                                    const char *service_name,
                                                    const char *object_path,
                                                    NMDBusConnectionSignalObjectMangerCb callback,
                                                    gpointer user_data,
                                                    GDestroyNotify user_data_free_func)
{
	SubscribeObjectManagerData *d;

	g_return_val_if_fail (callback, 0);

	d = g_slice_new (SubscribeObjectManagerData);
	*d = (SubscribeObjectManagerData) {
		.callback            = callback,
		.user_data           = user_data,
		.user_data_free_func = user_data_free_func,
	};

	return nm_dbus_connection_signal_subscribe_object_manager_plain (dbus_connection,
	                                                                 service_name,
	                                                                 object_path,
	                                                                 NULL,
	                                                                 _subscribe_object_manager_cb,
	                                                                 d,
	                                                                 _subscribe_object_manager_data_free);
}

/*****************************************************************************/

static void
_nm_dbus_connection_call_get_managed_objects_cb (GObject *source,
                                                 GAsyncResult *res,
                                                 gpointer user_data)
{
	gs_unref_variant GVariant *ret = NULL;
	gs_unref_variant GVariant *arg = NULL;
	gs_free_error GError *error = NULL;
	gpointer orig_user_data;
	NMDBusConnectionCallDefaultCb callback;

	nm_utils_user_data_unpack (user_data, &orig_user_data, &callback);

	ret = g_dbus_connection_call_finish (G_DBUS_CONNECTION (source), res, &error);

	nm_assert ((!!ret) != (!!error));

	if (ret) {
		nm_assert (g_variant_is_of_type (ret, G_VARIANT_TYPE ("(a{oa{sa{sv}}})")));
		arg = g_variant_get_child_value (ret, 0);
	}

	callback (arg, error, orig_user_data);
}

void
nm_dbus_connection_call_get_managed_objects (GDBusConnection *dbus_connection,
                                             const char *bus_name,
                                             const char *object_path,
                                             GDBusCallFlags flags,
                                             int timeout_msec,
                                             GCancellable *cancellable,
                                             NMDBusConnectionCallDefaultCb callback,
                                             gpointer user_data)
{
	nm_assert (callback);

	g_dbus_connection_call (dbus_connection,
	                        bus_name,
	                        object_path,
	                        DBUS_INTERFACE_OBJECT_MANAGER,
	                        "GetManagedObjects",
	                        NULL,
	                        G_VARIANT_TYPE ("(a{oa{sa{sv}}})"),
	                        flags,
	                        timeout_msec,
	                        cancellable,
	                        _nm_dbus_connection_call_get_managed_objects_cb,
	                        nm_utils_user_data_pack (user_data, callback));
}
