// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (C) 2017, 2018 Red Hat, Inc.
 */

#ifndef __NM_LIBNM_UTILS_H__
#define __NM_LIBNM_UTILS_H__

#include "c-list/src/c-list.h"
#include "nm-glib-aux/nm-ref-string.h"
#include "nm-glib-aux/nm-logging-fwd.h"
#include "nm-types.h"
#include "nm-object.h"
#include "nm-client.h"

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

char *nm_utils_fixup_vendor_string (const char *desc);
char *nm_utils_fixup_product_string (const char *desc);

char *nm_utils_wincaps_to_dash (const char *caps);

gboolean nm_utils_g_param_spec_is_default (const GParamSpec *pspec);

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

typedef enum {
	_NML_DBUS_LOG_LEVEL_NONE        = 0x00,

	_NML_DBUS_LOG_LEVEL_INITIALIZED = 0x01,

	_NML_DBUS_LOG_LEVEL_TRACE       = 0x02,

	_NML_DBUS_LOG_LEVEL_DEBUG       = 0x04,

	/* the difference between a warning and a critical is that it results in
	 * g_warning() vs. g_critical() messages. Note that we want to use "warnings"
	 * for unknown D-Bus API that could just result because we run against a
	 * newer NetworkManager version (such warnings are more graceful, because
	 * we want that libnm can be forward compatible against newer servers).
	 * Critial warnings should be emitted when NetworkManager exposes something
	 * on D-Bus that breaks the current expectations. Usually NetworkManager
	 * should not break API, hence such issues are more severe. */
	_NML_DBUS_LOG_LEVEL_WARN        = 0x08,
	_NML_DBUS_LOG_LEVEL_ERROR       = 0x10,

	/* ANY is only relevant for nml_dbus_log_enabled() to check whether any of the
	 * options is on. */
	NML_DBUS_LOG_LEVEL_ANY          = _NML_DBUS_LOG_LEVEL_INITIALIZED,

	NML_DBUS_LOG_LEVEL_TRACE        = _NML_DBUS_LOG_LEVEL_TRACE,
	NML_DBUS_LOG_LEVEL_DEBUG        =   _NML_DBUS_LOG_LEVEL_DEBUG
	                                  | NML_DBUS_LOG_LEVEL_TRACE,
	NML_DBUS_LOG_LEVEL_WARN         =   _NML_DBUS_LOG_LEVEL_WARN
	                                  | NML_DBUS_LOG_LEVEL_DEBUG,
	NML_DBUS_LOG_LEVEL_ERROR        =   _NML_DBUS_LOG_LEVEL_ERROR
	                                  | NML_DBUS_LOG_LEVEL_WARN,
} NMLDBusLogLevel;

#undef _LOGL_TRACE
#undef _LOGL_DEBUG
#undef _LOGL_INFO
#undef _LOGL_WARN
#undef _LOGL_ERR

#define _LOGL_TRACE NML_DBUS_LOG_LEVEL_TRACE
#define _LOGL_DEBUG NML_DBUS_LOG_LEVEL_DEBUG
#define _LOGL_INFO  NML_DBUS_LOG_LEVEL_INFO
#define _LOGL_WARN  NML_DBUS_LOG_LEVEL_WARN
#define _LOGL_ERR   NML_DBUS_LOG_LEVEL_ERR

extern volatile int _nml_dbus_log_level;

int _nml_dbus_log_level_init (void);

static inline gboolean
nml_dbus_log_enabled (NMLDBusLogLevel level)
{
	int l;

	nm_assert (NM_IN_SET (level, NML_DBUS_LOG_LEVEL_ANY,
	                             _NML_DBUS_LOG_LEVEL_NONE,
	                             NML_DBUS_LOG_LEVEL_TRACE,
	                             NML_DBUS_LOG_LEVEL_DEBUG,
	                             NML_DBUS_LOG_LEVEL_WARN,
	                             NML_DBUS_LOG_LEVEL_ERROR));

	l = g_atomic_int_get (&_nml_dbus_log_level);
	if (G_UNLIKELY (l == 0))
		l = _nml_dbus_log_level_init ();

	nm_assert (l & _NML_DBUS_LOG_LEVEL_INITIALIZED);
	if (level == NML_DBUS_LOG_LEVEL_ANY)
		return l != _NML_DBUS_LOG_LEVEL_INITIALIZED;
	return !!(((NMLDBusLogLevel) l) & level);
}

void _nml_dbus_log (NMLDBusLogLevel level,
                    const char *fmt,
                    ...) _nm_printf (2, 3);

#define NML_DBUS_LOG(level, ...) \
	G_STMT_START { \
		G_STATIC_ASSERT (   (level) == _NML_DBUS_LOG_LEVEL_NONE \
		                 || (level) == NML_DBUS_LOG_LEVEL_TRACE \
		                 || (level) == NML_DBUS_LOG_LEVEL_DEBUG \
		                 || (level) == NML_DBUS_LOG_LEVEL_WARN \
		                 || (level) == NML_DBUS_LOG_LEVEL_ERROR); \
		\
		if (nml_dbus_log_enabled (level)) { \
			_nml_dbus_log ((level), __VA_ARGS__); \
		} \
	} G_STMT_END

#define NML_DBUS_LOG_T(...) NML_DBUS_LOG (NML_DBUS_LOG_LEVEL_TRACE, __VA_ARGS__)
#define NML_DBUS_LOG_D(...) NML_DBUS_LOG (NML_DBUS_LOG_LEVEL_DEBUG, __VA_ARGS__)
#define NML_DBUS_LOG_W(...) NML_DBUS_LOG (NML_DBUS_LOG_LEVEL_WARN,  __VA_ARGS__)
#define NML_DBUS_LOG_E(...) NML_DBUS_LOG (NML_DBUS_LOG_LEVEL_ERROR, __VA_ARGS__)

/* _NML_NMCLIENT_LOG_LEVEL_COERCE is only for printf debugging. You can disable client logging by
 * mapping the requested log level to a different one (or disable it altogether).
 * That's useful for example if you are interested in *other* trace logging messages from
 * libnm and don't want to get flooded by NMClient's trace messages. */
#define _NML_NMCLIENT_LOG_LEVEL_COERCE(level) \
	/* for example, change condition below to suppress <trace> messages from NMClient. */ \
	((   TRUE \
	  || ((level) != NML_DBUS_LOG_LEVEL_TRACE)) \
	 ? (level) \
	 : _NML_DBUS_LOG_LEVEL_NONE)

#define NML_NMCLIENT_LOG(level, self, ...) \
	NML_DBUS_LOG (_NML_NMCLIENT_LOG_LEVEL_COERCE (level), \
	              "nmclient["NM_HASH_OBFUSCATE_PTR_FMT"]: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \
	              NM_HASH_OBFUSCATE_PTR (self) \
	              _NM_UTILS_MACRO_REST (__VA_ARGS__))

#define NML_NMCLIENT_LOG_T(self, ...) NML_NMCLIENT_LOG (NML_DBUS_LOG_LEVEL_TRACE, self, __VA_ARGS__)
#define NML_NMCLIENT_LOG_D(self, ...) NML_NMCLIENT_LOG (NML_DBUS_LOG_LEVEL_DEBUG, self, __VA_ARGS__)
#define NML_NMCLIENT_LOG_W(self, ...) NML_NMCLIENT_LOG (NML_DBUS_LOG_LEVEL_WARN,  self, __VA_ARGS__)
#define NML_NMCLIENT_LOG_E(self, ...) NML_NMCLIENT_LOG (NML_DBUS_LOG_LEVEL_ERROR, self, __VA_ARGS__)

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

static inline const char *
_nml_coerce_property_str_not_null (const char *str)
{
	return str ?: "";
}

static inline const char *
_nml_coerce_property_str_not_empty (const char *str)
{
	return str && str[0] ? str : NULL;
}

static inline const char *
_nml_coerce_property_object_path (NMRefString *path)
{
	if (!path)
		return NULL;
	return nm_dbus_path_not_empty (path->str);
}

static inline const char *const*
_nml_coerce_property_strv_not_null (char **strv)
{
	return ((const char *const*) strv) ?: NM_PTRARRAY_EMPTY (const char *);
}

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

GQuark nm_context_busy_watcher_quark (void);

void nm_context_busy_watcher_integrate_source (GMainContext *outer_context,
                                               GMainContext *inner_context,
                                               GObject *context_busy_watcher);

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

typedef struct {
	GCancellable *cancellable;
	GSource *cancel_on_idle_source;
	gulong cancelled_id;
	union {
		struct {
			GTask *task;
		} async;
		struct {
			GMainLoop *main_loop;
			GError **error_location;
		} sync;
	} data;
	bool is_sync:1;
} NMLInitData;

NMLInitData *nml_init_data_new_sync (GCancellable *cancellable,
                                     GMainLoop *main_loop,
                                     GError **error_location);

NMLInitData *nml_init_data_new_async (GCancellable *cancellable,
                                      GTask *task_take);

void nml_init_data_return (NMLInitData *init_data,
                           GError *error_take);

void nml_cleanup_context_busy_watcher_on_idle (GObject *context_busy_watcher_take,
                                               GMainContext *context);

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

typedef struct _NMLDBusObject     NMLDBusObject;
typedef struct _NMLDBusObjWatcher NMLDBusObjWatcher;
typedef struct _NMLDBusMetaIface  NMLDBusMetaIface;
typedef struct _NMLDBusPropertyO  NMLDBusPropertyO;
typedef struct _NMLDBusPropertyAO NMLDBusPropertyAO;

typedef enum {
	/* See comments below for NMLDBusMetaIface.interface_prio.
	 *
	 * Higher numbers means more important to detect the GObject type. */
	NML_DBUS_META_INTERFACE_PRIO_NONE             = 0,
	NML_DBUS_META_INTERFACE_PRIO_NMCLIENT         = 1,
	NML_DBUS_META_INTERFACE_PRIO_PARENT_TYPE      = 2,
	NML_DBUS_META_INTERFACE_PRIO_INSTANTIATE_LOW  = 3,
	NML_DBUS_META_INTERFACE_PRIO_INSTANTIATE_HIGH = 4,
} NMLDBusMetaInteracePrio;

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

#define NM_CLIENT_INSTANCE_FLAGS_ALL ((NMClientInstanceFlags) 0x1)

typedef struct {
	GType (*get_o_type_fcn) (void);

	/* Ignore whether the referenced NMObject is ready or not. That means,
	 * the property is always ready (and if the pointed object itself is
	 * not yet ready, the property pretends to be %NULL for the moment. */
	bool is_always_ready:1;

} NMLDBusPropertVTableO;

struct _NMLDBusPropertyO {
	NMLDBusObject *owner_dbobj;
	NMLDBusObjWatcher *obj_watcher;
	GObject *nmobj;
	const NMLDBusMetaIface *meta_iface;
	guint dbus_property_idx;
	bool is_ready:1;
	bool is_changed:1;
	bool block_is_changed:1;
};

gpointer nml_dbus_property_o_get_obj (NMLDBusPropertyO *pr_o);

gboolean nml_dbus_property_o_is_ready (const NMLDBusPropertyO *pr_o);

gboolean nml_dbus_property_o_is_ready_fully (const NMLDBusPropertyO *pr_o);

void nml_dbus_property_o_clear (NMLDBusPropertyO *pr_o,
                                NMClient *client);

void nml_dbus_property_o_clear_many (NMLDBusPropertyO *pr_o,
                                     guint len,
                                     NMClient *self);

void nml_dbus_property_o_notify_changed_many (NMLDBusPropertyO *ptr,
                                              guint len,
                                              NMClient *self);

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

typedef struct {
	GType (*get_o_type_fcn) (void);

	void (*notify_changed_ao) (NMLDBusPropertyAO *pr_ao,
	                           NMClient *self,
	                           NMObject *nmobj,
	                           gboolean is_added /* or else removed */);

	gboolean (*check_nmobj_visible_fcn) (GObject *nmobj);

	/* Ignore whether the referenced NMObject is ready or not. That means,
	 * the property is always ready (and if the pointed object itself is
	 * not yet ready, the property pretends to be %NULL for the moment. */
	bool is_always_ready:1;

} NMLDBusPropertVTableAO;

struct _NMLDBusPropertyAOData;

struct _NMLDBusPropertyAO {
	CList data_lst_head;
	GHashTable *hash;
	NMLDBusObject *owner_dbobj;
	const NMLDBusMetaIface *meta_iface;
	GPtrArray *arr;
	struct _NMLDBusPropertyAOData *changed_head;
	guint dbus_property_idx;
	guint n_not_ready;
	bool is_changed:1;
};

const GPtrArray *nml_dbus_property_ao_get_objs_as_ptrarray (NMLDBusPropertyAO *pr_ao);

gboolean nml_dbus_property_ao_is_ready (const NMLDBusPropertyAO *pr_ao);

void nml_dbus_property_ao_clear (NMLDBusPropertyAO *pr_ao,
                                 NMClient *client);

void nml_dbus_property_ao_clear_many (NMLDBusPropertyAO *pr_ao,
                                      guint len,
                                      NMClient *self);

void nml_dbus_property_ao_notify_changed_many (NMLDBusPropertyAO *ptr,
                                               guint len,
                                               NMClient *self);

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

typedef enum {
	NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NONE   =   0,
	NML_DBUS_NOTIFY_UPDATE_PROP_FLAGS_NOTIFY = 0x1,
} NMLDBusNotifyUpdatePropFlags;

NMLDBusNotifyUpdatePropFlags _nml_dbus_notify_update_prop_ignore (NMClient *client,
                                                                  NMLDBusObject *dbobj,
                                                                  const NMLDBusMetaIface *meta_iface,
                                                                  guint dbus_property_idx,
                                                                  GVariant *value);

NMLDBusNotifyUpdatePropFlags _nml_dbus_notify_update_prop_o (NMClient *client,
                                                             NMLDBusObject *dbobj,
                                                             const NMLDBusMetaIface *meta_iface,
                                                             guint dbus_property_idx,
                                                             GVariant *value);

typedef struct {
	const char *dbus_property_name;
	const GVariantType *dbus_type;

	guint16 prop_struct_offset;

	guint8 obj_properties_idx;

	bool use_notify_update_prop:1;

	bool obj_property_no_reverse_idx:1;

	union {
		union {
			const NMLDBusPropertVTableO *property_vtable_o;
			const NMLDBusPropertVTableAO *property_vtable_ao;
		} extra;

		NMLDBusNotifyUpdatePropFlags (*notify_update_prop) (NMClient *client,
		                                                    NMLDBusObject *dbobj,
		                                                    const NMLDBusMetaIface *meta_iface,
		                                                    guint dbus_property_idx,
		                                                    GVariant *value);
	};

} NMLDBusMetaProperty;

#define NML_DBUS_META_PROPERTY_INIT(v_dbus_property_name, \
                                    v_dbus_type, \
                                    v_obj_properties_idx, \
                                    ...) \
	{ \
		.dbus_property_name = ""v_dbus_property_name"", \
		.dbus_type          = NM_G_VARIANT_TYPE (""v_dbus_type""), \
		.obj_properties_idx = v_obj_properties_idx, \
		##__VA_ARGS__ \
	}

#define _NML_DBUS_META_PROPERTY_INIT_DEFAULT(v_dbus_type, \
                                             v_exp_type, \
                                             v_dbus_property_name, \
                                             v_obj_properties_idx, \
                                             v_container, \
                                             v_field) \
	NML_DBUS_META_PROPERTY_INIT (v_dbus_property_name, \
	                             v_dbus_type, \
	                             v_obj_properties_idx, \
	                             .prop_struct_offset = NM_STRUCT_OFFSET_ENSURE_TYPE (v_exp_type, v_container, v_field))

#define NML_DBUS_META_PROPERTY_INIT_B(...)  _NML_DBUS_META_PROPERTY_INIT_DEFAULT ("b",  bool,     __VA_ARGS__)
#define NML_DBUS_META_PROPERTY_INIT_Y(...)  _NML_DBUS_META_PROPERTY_INIT_DEFAULT ("y",  guint8,   __VA_ARGS__)
#define NML_DBUS_META_PROPERTY_INIT_Q(...)  _NML_DBUS_META_PROPERTY_INIT_DEFAULT ("q",  guint16,  __VA_ARGS__)
#define NML_DBUS_META_PROPERTY_INIT_I(...)  _NML_DBUS_META_PROPERTY_INIT_DEFAULT ("i",  gint32,   __VA_ARGS__)
#define NML_DBUS_META_PROPERTY_INIT_U(...)  _NML_DBUS_META_PROPERTY_INIT_DEFAULT ("u",  guint32,  __VA_ARGS__)
#define NML_DBUS_META_PROPERTY_INIT_X(...)  _NML_DBUS_META_PROPERTY_INIT_DEFAULT ("x",  gint64,   __VA_ARGS__)
#define NML_DBUS_META_PROPERTY_INIT_T(...)  _NML_DBUS_META_PROPERTY_INIT_DEFAULT ("t",  guint64,  __VA_ARGS__)
#define NML_DBUS_META_PROPERTY_INIT_S(...)  _NML_DBUS_META_PROPERTY_INIT_DEFAULT ("s",  char *,   __VA_ARGS__)
#define NML_DBUS_META_PROPERTY_INIT_AS(...) _NML_DBUS_META_PROPERTY_INIT_DEFAULT ("as", char **,  __VA_ARGS__)
#define NML_DBUS_META_PROPERTY_INIT_AY(...) _NML_DBUS_META_PROPERTY_INIT_DEFAULT ("ay", GBytes *, __VA_ARGS__)

#define NML_DBUS_META_PROPERTY_INIT_O(v_dbus_property_name, \
                                      v_obj_properties_idx, \
                                      v_container, \
                                      v_field) \
	NML_DBUS_META_PROPERTY_INIT (v_dbus_property_name, \
	                             "o", \
	                             v_obj_properties_idx, \
	                             .prop_struct_offset = NM_STRUCT_OFFSET_ENSURE_TYPE (NMRefString *, v_container, v_field), \
	                             .use_notify_update_prop = TRUE, \
	                             .notify_update_prop = _nml_dbus_notify_update_prop_o)

#define NML_DBUS_META_PROPERTY_INIT_O_PROP(v_dbus_property_name, \
                                           v_obj_properties_idx, \
                                           v_container, \
                                           v_field, \
                                           v_get_o_type_fcn, \
                                           ...) \
	NML_DBUS_META_PROPERTY_INIT (v_dbus_property_name, \
	                             "o", \
	                             v_obj_properties_idx, \
	                             .prop_struct_offset   = NM_STRUCT_OFFSET_ENSURE_TYPE (NMLDBusPropertyO, v_container, v_field), \
	                             .extra.property_vtable_o = &((const NMLDBusPropertVTableO) { \
	                                 .get_o_type_fcn = (v_get_o_type_fcn), \
	                                 ##__VA_ARGS__ \
	                             }))

#define NML_DBUS_META_PROPERTY_INIT_AO_PROP(v_dbus_property_name, \
                                            v_obj_properties_idx, \
                                            v_container, \
                                            v_field, \
                                            v_get_o_type_fcn, \
                                            ...) \
	NML_DBUS_META_PROPERTY_INIT (v_dbus_property_name, \
	                             "ao", \
	                             v_obj_properties_idx, \
	                             .prop_struct_offset   = NM_STRUCT_OFFSET_ENSURE_TYPE (NMLDBusPropertyAO, v_container, v_field), \
	                             .extra.property_vtable_ao = &((const NMLDBusPropertVTableAO) { \
	                                 .get_o_type_fcn = (v_get_o_type_fcn), \
	                                 ##__VA_ARGS__ \
	                             }))

#define NML_DBUS_META_PROPERTY_INIT_FCN(v_dbus_property_name, \
                                        v_obj_properties_idx, \
                                        v_dbus_type, \
                                        v_notify_update_prop, \
                                        ...) \
	NML_DBUS_META_PROPERTY_INIT (v_dbus_property_name, \
	                             v_dbus_type, \
	                             v_obj_properties_idx, \
	                             .use_notify_update_prop = TRUE, \
	                             .notify_update_prop     = (v_notify_update_prop), \
	                             ##__VA_ARGS__)

#define NML_DBUS_META_PROPERTY_INIT_IGNORE(v_dbus_property_name, \
                                           v_dbus_type) \
	NML_DBUS_META_PROPERTY_INIT (v_dbus_property_name, \
	                             v_dbus_type, \
	                             0, \
	                             .use_notify_update_prop = TRUE, \
	                             .notify_update_prop = _nml_dbus_notify_update_prop_ignore)

/* "TODO" is like "IGNORE". The difference is that we don't plan to ever implement "IGNORE", but
 * "TODO" is something we should add support for. */
#define NML_DBUS_META_PROPERTY_INIT_TODO(...) \
	NML_DBUS_META_PROPERTY_INIT_IGNORE (__VA_ARGS__)

struct _NMLDBusMetaIface {
	const char *dbus_iface_name;
	GType (*get_type_fcn) (void);

	/* Usually there is a one-to-one correspondence between the properties
	 * on D-Bus (dbus_properties) and the GObject properties (obj_properties).
	 *
	 * With:
	 *     meta_iface->obj_properties[o_idx]    (o_idx < n_obj_properties)
	 *     &meta_iface->dbus_properties[d_idx]  (d_idx < n_dbus_properties)
	 * it follows that
	 *     assert (meta_iface->obj_properties_reverse_idx[o_idx] == d_idx)
	 *     assert (meta_iface->dbus_properties[d_idx].obj_properties_idx == o_idx)
	 * if (and only if) two properties correspond.
	 */
	const GParamSpec *const*obj_properties;
	const NMLDBusMetaProperty *dbus_properties;
	const guint8 *obj_properties_reverse_idx;

	guint8 n_dbus_properties;
	guint8 n_obj_properties;

	/* The offsets "prop_struct_offset" in NMLDBusMetaProperty are based on some base
	 * struct. If "base_struct_offset" is 0, then the base struct is the GObject pointer
	 * itself.
	 * If this is non-null, then we expect at that location a pointer to the offset.
	 * In this case we need to first find the base pointer via
	 *   *((gpointer *) ((char *) nmobj + meta_iface->base_struct_offset)).
	 *
	 * This covers NMDeviceBridge._priv vs. NMDevice._priv. In the second case,
	 * _priv is a pointer that we first need to follow.
	 */
	guint8 base_struct_offset;

	/* We create the appropriate NMObject GType based on the D-Bus interfaces that
	 * are present. For example, if we see a "org.freedesktop.NetworkManager.Device.Bridge"
	 * interface, we create a NMDeviceBridge. Basically, if it looks like a certain
	 * object (based on the D-Bus interface), we assume it is.
	 *
	 * Some interfaces are purely additional ("org.freedesktop.NetworkManager.Device.Statistics")
	 * and don't determine the NMObject type (%NML_DBUS_META_INTERFACE_PRIO_NONE).
	 *
	 * Some interfaces are of a parent type ("org.freedesktop.NetworkManager.Device" for
	 * NMDevice), and don't determine the type either (%NML_DBUS_META_INTERFACE_PRIO_PARENT_TYPE).
	 *
	 * Some interfaces ("org.freedesktop.NetworkManager.AgentManager") belong to NMClient
	 * itself. Those have priority %NML_DBUS_META_INTERFACE_PRIO_NMCLIENT.
	 *
	 * In most cases, each D-Bus object is expected to have only one D-Bus interface
	 * to determine the type. While theoretically an object
	 * "/org/freedesktop/NetworkManager/Devices/3" could have interfaces "org.freedesktop.NetworkManager.Device.Bridge"
	 * and "org.freedesktop.NetworkManager.Device.Bond" at the same time, in practice it doesn't.
	 * Note that we also assume that once a D-Bus object gets a NMObject, it cannot change (*).
	 * NetworkManager's API does not add/remove interfaces after exporting the object the
	 * first time, so in practice each D-Bus object is expected to have a suitable D-Bus
	 * interface (and only determining interface, which doesn't change). Those interfaces have
	 * priority %NML_DBUS_META_INTERFACE_PRIO_INSTANTIATE_HIGH.
	 *
	 * (*) note that nothing bad would happen if a faulty NetworkManager would violate that.
	 * Of course, something would not work correctly, but the D-Bus interface we find is unexpected
	 * and wrong.
	 *
	 * One exception is "org.freedesktop.NetworkManager.Connection.Active". This can either
	 * be a NMActiveConnection or a NMVpnConnection. Hence, this profile has priority
	 * %NML_DBUS_META_INTERFACE_PRIO_INSTANTIATE_LOW, and depending on whether there is
	 * a "org.freedesktop.NetworkManager.VPN.Connection" (with high priority), we create
	 * one or the other type.
	 */
	NMLDBusMetaInteracePrio interface_prio:3;
};

#define NML_DBUS_META_IFACE_OBJ_PROPERTIES() \
	.obj_properties             = (const GParamSpec *const*) (obj_properties), \
	.n_obj_properties           = _PROPERTY_ENUMS_LAST, \
	.obj_properties_reverse_idx = ((guint8 [_PROPERTY_ENUMS_LAST]) { })

#define NML_DBUS_META_IFACE_DBUS_PROPERTIES(...) \
	.dbus_properties            = ((const NMLDBusMetaProperty []) { __VA_ARGS__ }), \
	.n_dbus_properties          = (sizeof ((const NMLDBusMetaProperty []) { __VA_ARGS__ }) / sizeof (NMLDBusMetaProperty))

#define NML_DBUS_META_IFACE_INIT(v_dbus_iface_name, \
                                 v_get_type_fcn, \
                                 v_interface_prio, \
                                 ...) \
	{ \
		.dbus_iface_name            = ""v_dbus_iface_name"", \
		.get_type_fcn               = v_get_type_fcn, \
		.interface_prio             = v_interface_prio, \
		##__VA_ARGS__ \
	}

#define NML_DBUS_META_IFACE_INIT_PROP(v_dbus_iface_name, \
                                      v_get_type_fcn, \
                                      v_interface_prio, \
                                      ...) \
	NML_DBUS_META_IFACE_INIT (v_dbus_iface_name, \
	                          v_get_type_fcn, \
	                          v_interface_prio, \
	                          NML_DBUS_META_IFACE_OBJ_PROPERTIES (), \
	                          ##__VA_ARGS__)

extern const NMLDBusMetaIface *const _nml_dbus_meta_ifaces[44];

extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_accesspoint;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_agentmanager;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_checkpoint;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_connection_active;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_adsl;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_bluetooth;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_bond;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_bridge;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_dummy;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_generic;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_infiniband;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_iptunnel;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_lowpan;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_macsec;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_macvlan;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_modem;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_olpcmesh;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_ovsbridge;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_ovsinterface;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_ovsport;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_ppp;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_statistics;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_team;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_tun;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_veth;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_vlan;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_vrf;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_vxlan;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_wifip2p;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_wired;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_wireguard;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_wireless;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_device_wpan;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_dhcp4config;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_dhcp6config;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_dnsmanager;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_ip4config;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_ip6config;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_settings;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_settings_connection;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_vpn_connection;
extern const NMLDBusMetaIface _nml_dbus_meta_iface_nm_wifip2ppeer;

const NMLDBusMetaIface *nml_dbus_meta_iface_get (const char *dbus_iface_name);

const NMLDBusMetaProperty *nml_dbus_meta_property_get (const NMLDBusMetaIface *meta_iface,
                                                       const char *dbus_property_name,
                                                       guint *out_idx);

void _nml_dbus_meta_class_init_with_properties_impl (GObjectClass *object_class, const NMLDBusMetaIface *const*meta_iface);
#define _nml_dbus_meta_class_init_with_properties(object_class, ...) \
	_nml_dbus_meta_class_init_with_properties_impl ((object_class), ((const NMLDBusMetaIface *const[]) { __VA_ARGS__, NULL }))

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

typedef enum {
	NML_DBUS_OBJ_STATE_UNLINKED = 0,
	NML_DBUS_OBJ_STATE_WATCHED_ONLY,
	NML_DBUS_OBJ_STATE_ON_DBUS,
	NML_DBUS_OBJ_STATE_WITH_NMOBJ_NOT_READY,
	NML_DBUS_OBJ_STATE_WITH_NMOBJ_READY,
} NMLDBusObjState;

typedef enum {
	NML_DBUS_OBJ_CHANGED_TYPE_NONE  = 0,
	NML_DBUS_OBJ_CHANGED_TYPE_DBUS  = (1LL <<  0),
	NML_DBUS_OBJ_CHANGED_TYPE_NMOBJ = (1LL <<  1),
} NMLDBusObjChangedType;

struct _NMLDBusObject {
	NMRefString *dbus_path;

	/* While the object is tracked by NMClient, it is linked with this list.
	 * The lists are partitioned based on the NMLDBusObjState. */
	CList dbus_objects_lst;

	/* The list of D-Bus interface NMLDBusObjIfaceData.
	 *
	 * Some may be about to be removed (iface_removed) or
	 * unknown (!dbus_iface_is_wellknown). */
	CList iface_lst_head;

	/* The list of registered NMLDBusObjWatcher. */
	CList watcher_lst_head;

	/* When an object changes (e.g. because of new information on D-Bus), we often
	 * don't process the changes right away, but enqueue the object in a changed
	 * list. This list goes together with obj_changed_type property below, which
	 * tracks what changed. */
	CList obj_changed_lst;

	GObject *nmobj;

	int ref_count;

	NMLDBusObjState obj_state:4;

	NMLDBusObjChangedType obj_changed_type:3;
};

static inline gboolean
NML_IS_DBUS_OBJECT (NMLDBusObject *dbobj)
{
	nm_assert (   !dbobj
	           || (   NM_IS_REF_STRING (dbobj->dbus_path)
	               && dbobj->ref_count > 0));
	nm_assert (   !dbobj
	           || !dbobj->nmobj
	           || NM_IS_OBJECT (dbobj->nmobj)
	           || NM_IS_CLIENT (dbobj->nmobj));
	return !!dbobj;
}

NMLDBusObject *nml_dbus_object_ref (NMLDBusObject *dbobj);

void nml_dbus_object_unref (NMLDBusObject *dbobj);

NM_AUTO_DEFINE_FCN0 (NMLDBusObject *, _nm_auto_unref_nml_dbusobj, nml_dbus_object_unref)
#define nm_auto_unref_nml_dbusobj nm_auto (_nm_auto_unref_nml_dbusobj)

gpointer nml_dbus_object_get_property_location (NMLDBusObject *dbobj,
                                                const NMLDBusMetaIface *meta_iface,
                                                const NMLDBusMetaProperty *meta_property);

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

/* NMClient is not an NMObject, but in some aspects we want to track it like
 * an NMObject. For that, both NMClient and NMObject "implement" NMObjectBase,
 * despite not actually implementing such a GObject type. */
typedef struct {
	GObject parent;
	CList queue_notify_lst;
	bool is_disposing:1;
} NMObjectBase;

typedef struct {
	GObjectClass parent;
} NMObjectBaseClass;

struct _NMObjectPrivate;

struct _NMObject {
	union {
		GObject parent;
		NMObjectBase obj_base;
	};
	struct _NMObjectPrivate *_priv;
};

typedef struct _NMObjectClassFieldInfo {
	const struct _NMObjectClassFieldInfo *parent;
	NMObjectClass *klass;
	guint16 offset;
	guint16 num;
} _NMObjectClassFieldInfo;

struct _NMObjectClass {
	union {
		GObjectClass parent;
		NMObjectBaseClass obj_base;
	};

	void (*register_client) (NMObject *self,
	                         NMClient *client,
	                         NMLDBusObject *dbobj);

	void (*unregister_client) (NMObject *self,
	                           NMClient *client,
	                           NMLDBusObject *dbobj);

	gboolean (*is_ready) (NMObject *self);

	void (*obj_changed_notify) (NMObject *self);

	const _NMObjectClassFieldInfo *property_o_info;
	const _NMObjectClassFieldInfo *property_ao_info;

	guint16 priv_ptr_offset;

	bool priv_ptr_indirect:1;
};

#define _NM_OBJECT_CLASS_INIT_PRIV_PTR_DIRECT(nm_object_class, type_name) \
	G_STMT_START { \
		(nm_object_class)->priv_ptr_offset = NM_STRUCT_OFFSET_ENSURE_TYPE (type_name##Private, type_name, _priv); \
		(nm_object_class)->priv_ptr_indirect = FALSE; \
	} G_STMT_END

#define _NM_OBJECT_CLASS_INIT_PRIV_PTR_INDIRECT(nm_object_class, type_name) \
	G_STMT_START { \
		(nm_object_class)->priv_ptr_offset = NM_STRUCT_OFFSET_ENSURE_TYPE (type_name##Private *, type_name, _priv); \
		(nm_object_class)->priv_ptr_indirect = TRUE; \
	} G_STMT_END

#define _NM_OBJECT_CLASS_INIT_FIELD_INFO(_nm_object_class, _field_name, _offset, _num) \
	G_STMT_START { \
		(_nm_object_class)->_field_name = ({ \
			static _NMObjectClassFieldInfo _f; \
			\
			_f = (_NMObjectClassFieldInfo) { \
				.parent = (_nm_object_class)->_field_name, \
				.klass  = (_nm_object_class), \
				.offset = _offset, \
				.num    = _num, \
			}; \
			&_f; \
		}); \
	} G_STMT_END

#define _NM_OBJECT_CLASS_INIT_PROPERTY_O_FIELDS_1(nm_object_class, type_name, field_name) \
	_NM_OBJECT_CLASS_INIT_FIELD_INFO (nm_object_class, \
	                                  property_o_info, \
	                                  NM_STRUCT_OFFSET_ENSURE_TYPE (NMLDBusPropertyO, type_name, field_name), \
	                                  1)

#define _NM_OBJECT_CLASS_INIT_PROPERTY_O_FIELDS_N(nm_object_class, type_name, field_name) \
	_NM_OBJECT_CLASS_INIT_FIELD_INFO (nm_object_class, \
	                                  property_o_info, \
	                                  NM_STRUCT_OFFSET_ENSURE_TYPE (NMLDBusPropertyO *, type_name, field_name), \
	                                  G_N_ELEMENTS (((type_name *) NULL)->field_name))

#define _NM_OBJECT_CLASS_INIT_PROPERTY_AO_FIELDS_1(nm_object_class, type_name, field_name) \
	_NM_OBJECT_CLASS_INIT_FIELD_INFO (nm_object_class, \
	                                  property_ao_info, \
	                                  NM_STRUCT_OFFSET_ENSURE_TYPE (NMLDBusPropertyAO, type_name, field_name), \
	                                  1)

#define _NM_OBJECT_CLASS_INIT_PROPERTY_AO_FIELDS_N(nm_object_class, type_name, field_name) \
	_NM_OBJECT_CLASS_INIT_FIELD_INFO (nm_object_class, \
	                                  property_ao_info, \
	                                  NM_STRUCT_OFFSET_ENSURE_TYPE (NMLDBusPropertyAO *, type_name, field_name), \
	                                  G_N_ELEMENTS (((type_name *) NULL)->field_name))


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

struct _NMDevicePrivate;

struct _NMDevice {
	NMObject parent;
	struct _NMDevicePrivate *_priv;
};

struct _NMDeviceClass {
	struct _NMObjectClass parent;

	gboolean (*connection_compatible) (NMDevice *device,
	                                   NMConnection *connection,
	                                   GError **error);

	const char *(*get_type_description) (NMDevice *device);

	GType (*get_setting_type) (NMDevice *device);
};

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

struct _NMActiveConnectionPrivate;

struct _NMActiveConnection {
	NMObject parent;
	struct _NMActiveConnectionPrivate *_priv;
};

struct _NMActiveConnectionClass {
	struct _NMObjectClass parent;
};

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

struct _NMDhcpConfigPrivate;

struct _NMDhcpConfig {
	NMObject parent;
	struct _NMDhcpConfigPrivate *_priv;
};

struct _NMDhcpConfigClass {
	struct _NMObjectClass parent;
};

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

struct _NMIPConfigPrivate;

struct _NMIPConfig {
	NMObject parent;
	struct _NMIPConfigPrivate *_priv;
};

struct _NMIPConfigClass {
	struct _NMObjectClass parent;
};

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

NMLDBusObject *_nm_object_get_dbobj (gpointer self);

const char *_nm_object_get_path (gpointer self);

NMClient *_nm_object_get_client (gpointer self);

GDBusConnection *_nm_client_get_dbus_connection (NMClient *client);

const char *_nm_client_get_dbus_name_owner (NMClient *client);

GMainContext *_nm_client_get_context_main (NMClient *client);
GMainContext *_nm_client_get_context_dbus (NMClient *client);

void _nm_client_queue_notify_object (NMClient *client,
                                     gpointer nmobj,
                                     const GParamSpec *pspec);

void _nm_client_notify_object_changed (NMClient *self,
                                       NMLDBusObject *dbobj);

struct udev *_nm_client_get_udev (NMClient *self);

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

#define NM_CLIENT_NOTIFY_EVENT_PRIO_BEFORE (-100)
#define NM_CLIENT_NOTIFY_EVENT_PRIO_GPROP      0
#define NM_CLIENT_NOTIFY_EVENT_PRIO_AFTER    100

typedef struct _NMClientNotifyEvent NMClientNotifyEvent;

typedef void (*NMClientNotifyEventCb) (NMClient *self,
                                       gpointer notify_event);

struct _NMClientNotifyEvent {
	CList lst;
	NMClientNotifyEventCb callback;
	int priority;
};

gpointer _nm_client_notify_event_queue (NMClient *self,
                                        int priority,
                                        NMClientNotifyEventCb callback,
                                        gsize event_size);

typedef struct _NMClientNotifyEventWithPtr NMClientNotifyEventWithPtr;

typedef void (*NMClientNotifyEventWithPtrCb) (NMClient *self,
                                              NMClientNotifyEventWithPtr *notify_event);

struct _NMClientNotifyEventWithPtr {
	NMClientNotifyEvent parent;
	gpointer user_data;
};

NMClientNotifyEventWithPtr *_nm_client_notify_event_queue_with_ptr (NMClient *self,
                                                                    int priority,
                                                                    NMClientNotifyEventWithPtrCb callback,
                                                                    gpointer user_data);

void _nm_client_notify_event_queue_emit_obj_signal (NMClient *self,
                                                    GObject *source,
                                                    NMObject *nmobj,
                                                    gboolean is_added /* or else removed */,
                                                    int prio_offset,
                                                    guint signal_id);

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

GError *_nm_client_new_error_nm_not_running (void);
GError *_nm_client_new_error_nm_not_cached (void);

void _nm_client_dbus_call_simple (NMClient *self,
                                  GCancellable *cancellable,
                                  const char *object_path,
                                  const char *interface_name,
                                  const char *method_name,
                                  GVariant *parameters,
                                  const GVariantType *reply_type,
                                  GDBusCallFlags flags,
                                  int timeout_msec,
                                  GAsyncReadyCallback callback,
                                  gpointer user_data);

void _nm_client_dbus_call (NMClient *self,
                           gpointer source_obj,
                           gpointer source_tag,
                           GCancellable *cancellable,
                           GAsyncReadyCallback user_callback,
                           gpointer user_callback_data,
                           const char *object_path,
                           const char *interface_name,
                           const char *method_name,
                           GVariant *parameters,
                           const GVariantType *reply_type,
                           GDBusCallFlags flags,
                           int timeout_msec,
                           GAsyncReadyCallback internal_callback);

GVariant *_nm_client_dbus_call_sync (NMClient *self,
                                     GCancellable *cancellable,
                                     const char *object_path,
                                     const char *interface_name,
                                     const char *method_name,
                                     GVariant *parameters,
                                     const GVariantType *reply_type,
                                     GDBusCallFlags flags,
                                     int timeout_msec,
                                     gboolean strip_dbus_error,
                                     GError **error);

gboolean _nm_client_dbus_call_sync_void (NMClient *self,
                                         GCancellable *cancellable,
                                         const char *object_path,
                                         const char *interface_name,
                                         const char *method_name,
                                         GVariant *parameters,
                                         GDBusCallFlags flags,
                                         int timeout_msec,
                                         gboolean strip_dbus_error,
                                         GError **error);

void _nm_client_set_property_sync_legacy (NMClient *self,
                                          const char *object_path,
                                          const char *interface,
                                          const char *prop_name,
                                          const char *format_string,
                                          ...);

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

void _nm_client_get_settings_call (NMClient *self,
                                   NMLDBusObject *dbobj);

GCancellable *_nm_remote_settings_get_settings_prepare (NMRemoteConnection *self);

void _nm_remote_settings_get_settings_commit (NMRemoteConnection *self,
                                              GVariant *settings);

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

void _nm_active_connection_state_changed_commit (NMActiveConnection *self,
                                                 guint32 state,
                                                 guint32 reason);

void _nm_vpn_connection_state_changed_commit (NMVpnConnection *self,
                                              guint32 state,
                                              guint32 reason);

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

NMLDBusNotifyUpdatePropFlags
_nm_device_notify_update_prop_hw_address (NMClient *client,
                                          NMLDBusObject *dbobj,
                                          const NMLDBusMetaIface *meta_iface,
                                          guint dbus_property_idx,
                                          GVariant *value);

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

#endif /* __NM_LIBNM_UTILS_H__ */
