// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (C) 2007 - 2011 Red Hat, Inc.
 * Copyright (C) 2007 - 2008 Novell, Inc.
 */

#include "nm-default.h"

#include "nm-setting.h"

#include "nm-setting-private.h"
#include "nm-utils.h"
#include "nm-core-internal.h"
#include "nm-utils-private.h"
#include "nm-property-compare.h"

/**
 * SECTION:nm-setting
 * @short_description: Describes related configuration information
 *
 * Each #NMSetting contains properties that describe configuration that applies
 * to a specific network layer (like IPv4 or IPv6 configuration) or device type
 * (like Ethernet, or Wi-Fi).  A collection of individual settings together
 * make up an #NMConnection. Each property is strongly typed and usually has
 * a number of allowed values.  See each #NMSetting subclass for a description
 * of properties and allowed values.
 */

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

typedef struct {
	GHashTable *hash;
	const char **names;
	GVariant **values;
} GenData;

typedef struct {
	const char *name;
	GType type;
	NMSettingPriority priority;
} SettingInfo;

NM_GOBJECT_PROPERTIES_DEFINE (NMSetting,
	PROP_NAME,
);

typedef struct {
	GenData *gendata;
} NMSettingPrivate;

G_DEFINE_ABSTRACT_TYPE (NMSetting, nm_setting, G_TYPE_OBJECT)

#define NM_SETTING_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_SETTING, NMSettingPrivate))

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

static GenData *_gendata_hash (NMSetting *setting, gboolean create_if_necessary);

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

static NMSettingPriority
_get_base_type_priority (const NMMetaSettingInfo *setting_info,
                         GType gtype)
{
	/* Historical oddity: PPPoE is a base-type even though it's not
	 * priority 1.  It needs to be sorted *after* lower-level stuff like
	 * Wi-Fi security or 802.1x for secrets, but it's still allowed as a
	 * base type.
	 */

	if (setting_info) {
		if (   NM_IN_SET (setting_info->setting_priority,
		                  NM_SETTING_PRIORITY_HW_BASE,
		                  NM_SETTING_PRIORITY_HW_NON_BASE)
		    || gtype == NM_TYPE_SETTING_PPPOE)
			return setting_info->setting_priority;
	}

	return NM_SETTING_PRIORITY_INVALID;
}

NMSettingPriority
_nm_setting_get_setting_priority (NMSetting *setting)
{
	const NMMetaSettingInfo *setting_info;

	g_return_val_if_fail (NM_IS_SETTING (setting), NM_SETTING_PRIORITY_INVALID);

	setting_info = NM_SETTING_GET_CLASS (setting)->setting_info;
	return setting_info ? setting_info->setting_priority : NM_SETTING_PRIORITY_INVALID;
}

NMSettingPriority
_nm_setting_type_get_base_type_priority (GType type)
{
	return _get_base_type_priority (nm_meta_setting_infos_by_gtype (type),
	                                type);
}

NMSettingPriority
_nm_setting_get_base_type_priority (NMSetting *setting)
{
	g_return_val_if_fail (NM_IS_SETTING (setting), NM_SETTING_PRIORITY_INVALID);

	return _get_base_type_priority (NM_SETTING_GET_CLASS (setting)->setting_info,
	                                G_OBJECT_TYPE (setting));
}

/**
 * nm_setting_lookup_type:
 * @name: a setting name
 *
 * Returns the #GType of the setting's class for a given setting name.
 *
 * Returns: the #GType of the setting's class, or %G_TYPE_INVALID if
 *   @name is not recognized.
 **/
GType
nm_setting_lookup_type (const char *name)
{
	const NMMetaSettingInfo *setting_info;

	g_return_val_if_fail (name, G_TYPE_INVALID);

	setting_info = nm_meta_setting_infos_by_name (name);
	return setting_info ? setting_info->get_setting_gtype () : G_TYPE_INVALID;
}

int
_nm_setting_compare_priority (gconstpointer a, gconstpointer b)
{
	NMSettingPriority prio_a, prio_b;

	prio_a = _nm_setting_get_setting_priority ((NMSetting *) a);
	prio_b = _nm_setting_get_setting_priority ((NMSetting *) b);

	if (prio_a < prio_b)
		return -1;
	else if (prio_a == prio_b)
		return 0;
	return 1;
}

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

gboolean
_nm_setting_slave_type_is_valid (const char *slave_type, const char **out_port_type)
{
	const char *port_type = NULL;
	gboolean found = TRUE;

	if (!slave_type)
		found = FALSE;
	else if (NM_IN_STRSET (slave_type,
	                       NM_SETTING_BOND_SETTING_NAME,
	                       NM_SETTING_VRF_SETTING_NAME)) {
		/* pass */
	}
	else if (nm_streq (slave_type, NM_SETTING_BRIDGE_SETTING_NAME))
		port_type = NM_SETTING_BRIDGE_PORT_SETTING_NAME;
	else if (nm_streq (slave_type, NM_SETTING_OVS_BRIDGE_SETTING_NAME))
		port_type = NM_SETTING_OVS_PORT_SETTING_NAME;
	else if (nm_streq (slave_type, NM_SETTING_OVS_PORT_SETTING_NAME))
		port_type = NM_SETTING_OVS_INTERFACE_SETTING_NAME;
	else if (nm_streq (slave_type, NM_SETTING_TEAM_SETTING_NAME))
		port_type = NM_SETTING_TEAM_PORT_SETTING_NAME;
	else
		found = FALSE;

	if (out_port_type)
		*out_port_type = port_type;
	return found;
}

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

static const NMSettInfoProperty *
_nm_sett_info_property_find_in_array (const NMSettInfoProperty *properties, guint len, const char *name)
{
	guint i;

	for (i = 0; i < len; i++) {
		if (nm_streq (name, properties[i].name))
			return &properties[i];
	}
	return NULL;
}

static GVariant *
_gprop_to_dbus_fcn_bytes (const GValue *val)
{
	nm_assert (G_VALUE_HOLDS (val, G_TYPE_BYTES));
	return nm_utils_gbytes_to_variant_ay (g_value_get_boxed (val));
}

static GVariant *
_gprop_to_dbus_fcn_enum (const GValue *val)
{
	return g_variant_new_int32 (g_value_get_enum (val));
}

static GVariant *
_gprop_to_dbus_fcn_flags (const GValue *val)
{
	return g_variant_new_uint32 (g_value_get_flags (val));
}

gboolean
_nm_properties_override_assert (const NMSettInfoProperty *prop_info)
{
	nm_assert (prop_info);
	nm_assert ((!!prop_info->name) != (!!prop_info->param_spec));
	nm_assert (!prop_info->param_spec || !prop_info->name || nm_streq0 (prop_info->name, prop_info->param_spec->name));

#define _PROPERT_EXTRA(prop_info, member) \
	({ \
		const NMSettInfoProperty *_prop_info = (prop_info); \
		\
		(_prop_info->property_type ? _prop_info->property_type->member : 0); \
	})

	nm_assert (!_PROPERT_EXTRA (prop_info, gprop_from_dbus_fcn) || _PROPERT_EXTRA (prop_info, dbus_type));
	nm_assert (!_PROPERT_EXTRA (prop_info, from_dbus_fcn)       || _PROPERT_EXTRA (prop_info, dbus_type));
	nm_assert (!_PROPERT_EXTRA (prop_info, to_dbus_fcn)         || _PROPERT_EXTRA (prop_info, dbus_type));

	nm_assert (!_PROPERT_EXTRA (prop_info, to_dbus_fcn)   || !_PROPERT_EXTRA (prop_info, gprop_to_dbus_fcn));
	nm_assert (!_PROPERT_EXTRA (prop_info, from_dbus_fcn) || !_PROPERT_EXTRA (prop_info, gprop_from_dbus_fcn));

	nm_assert (!_PROPERT_EXTRA (prop_info, gprop_to_dbus_fcn)   || prop_info->param_spec);
	nm_assert (!_PROPERT_EXTRA (prop_info, gprop_from_dbus_fcn) || prop_info->param_spec);

#undef _PROPERT_EXTRA

	return TRUE;
}

static NMSettInfoSetting _sett_info_settings[_NM_META_SETTING_TYPE_NUM];

const NMSettInfoSetting *
nmtst_sett_info_settings (void)
{
	return _sett_info_settings;
}

static int
_property_infos_sort_cmp_setting_connection (gconstpointer p_a,
                                             gconstpointer p_b,
                                             gpointer user_data)
{
	const NMSettInfoProperty *a = *((const NMSettInfoProperty *const*) p_a);
	const NMSettInfoProperty *b = *((const NMSettInfoProperty *const*) p_b);
	int c_name;

	c_name = strcmp (a->name, b->name);
	nm_assert (c_name != 0);

#define CMP_AND_RETURN(n_a, n_b, name) \
	G_STMT_START { \
		gboolean _is = nm_streq (n_a, ""name); \
		\
		if (   _is \
		    || nm_streq (n_b, ""name)) \
			return _is ? -1 : 1; \
	} G_STMT_END

	/* for [connection], report first id, uuid, type in that order. */
	if (c_name != 0) {
		CMP_AND_RETURN (a->name, b->name, NM_SETTING_CONNECTION_ID);
		CMP_AND_RETURN (a->name, b->name, NM_SETTING_CONNECTION_UUID);
		CMP_AND_RETURN (a->name, b->name, NM_SETTING_CONNECTION_TYPE);
	}

#undef CMP_AND_RETURN

	return c_name;
}

static const NMSettInfoProperty *const*
_property_infos_sort (const NMSettInfoProperty *property_infos,
                      guint property_infos_len,
                      NMSettingClass *setting_class)
{
	const NMSettInfoProperty **arr;
	guint i;

#if NM_MORE_ASSERTS > 5
	/* assert that the property names are all unique and sorted. */
	for (i = 0; i < property_infos_len; i++) {
		if (property_infos[i].param_spec)
			nm_assert (nm_streq (property_infos[i].name, property_infos[i].param_spec->name));
		if (i > 0)
			nm_assert (strcmp (property_infos[i - 1].name, property_infos[i].name) < 0);
	}
#endif

	if (property_infos_len <= 1)
		return NULL;
	if (G_TYPE_FROM_CLASS (setting_class) != NM_TYPE_SETTING_CONNECTION) {
		/* we only do something special for certain setting types. This one,
		 * has just alphabetical sorting. */
		return NULL;
	}

	arr = g_new (const NMSettInfoProperty *, property_infos_len);
	for (i = 0; i < property_infos_len; i++)
		arr[i] = &property_infos[i];

	g_qsort_with_data (arr,
	                   property_infos_len,
	                   sizeof (const NMSettInfoProperty *),
	                   _property_infos_sort_cmp_setting_connection,
	                   NULL);
	return arr;
}

void
_nm_setting_class_commit_full (NMSettingClass *setting_class,
                               NMMetaSettingType meta_type,
                               const NMSettInfoSettDetail *detail,
                               GArray *properties_override)
{
	NMSettInfoSetting *sett_info;
	gs_free GParamSpec **property_specs = NULL;
	guint i, n_property_specs, override_len;

	nm_assert (NM_IS_SETTING_CLASS (setting_class));
	nm_assert (!setting_class->setting_info);

	nm_assert (meta_type < G_N_ELEMENTS (_sett_info_settings));

	sett_info = &_sett_info_settings[meta_type];

	nm_assert (!sett_info->setting_class);
	nm_assert (!sett_info->property_infos_len);
	nm_assert (!sett_info->property_infos);

	if (!properties_override) {
		override_len = 0;
		properties_override = _nm_sett_info_property_override_create_array ();
	} else
		override_len = properties_override->len;

	property_specs = g_object_class_list_properties (G_OBJECT_CLASS (setting_class),
	                                                 &n_property_specs);

	for (i = 0; i < properties_override->len; i++) {
		NMSettInfoProperty *p = &g_array_index (properties_override, NMSettInfoProperty, i);

		nm_assert ((!!p->name) != (!!p->param_spec));

		if (!p->name) {
			nm_assert (p->param_spec);
			p->name = p->param_spec->name;
		} else
			nm_assert (!p->param_spec);
	}

#if NM_MORE_ASSERTS > 10
	/* assert that properties_override is constructed consistently. */
	for (i = 0; i < override_len; i++) {
		const NMSettInfoProperty *p = &g_array_index (properties_override, NMSettInfoProperty, i);
		gboolean found = FALSE;
		guint j;

		nm_assert (!_nm_sett_info_property_find_in_array ((NMSettInfoProperty *) properties_override->data,
		                                                  i,
		                                                  p->name));
		for (j = 0; j < n_property_specs; j++) {
			if (!nm_streq (property_specs[j]->name, p->name))
				continue;
			nm_assert (!found);
			found = TRUE;
			nm_assert (p->param_spec == property_specs[j]);
		}
		nm_assert (found == (p->param_spec != NULL));
	}
#endif

	for (i = 0; i < n_property_specs; i++) {
		const char *name = property_specs[i]->name;
		NMSettInfoProperty *p;

		if (_nm_sett_info_property_find_in_array ((NMSettInfoProperty *) properties_override->data,
		                                           override_len,
		                                           name))
			continue;

		g_array_set_size (properties_override, properties_override->len + 1);
		p = &g_array_index (properties_override, NMSettInfoProperty, properties_override->len - 1);
		memset (p, 0, sizeof (*p));
		p->name = name;
		p->param_spec = property_specs[i];
	}

	for (i = 0; i < properties_override->len; i++) {
		NMSettInfoProperty *p = &g_array_index (properties_override, NMSettInfoProperty, i);
		GType vtype;

		if (p->property_type)
			goto has_property_type;

		nm_assert (p->param_spec);

		vtype = p->param_spec->value_type;
		if (vtype == G_TYPE_BOOLEAN)
			p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_BOOLEAN);
		else if (vtype == G_TYPE_UCHAR)
			p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_BYTE);
		else if (vtype == G_TYPE_INT)
			p->property_type = &nm_sett_info_propert_type_plain_i;
		else if (vtype == G_TYPE_UINT)
			p->property_type = &nm_sett_info_propert_type_plain_u;
		else if (vtype == G_TYPE_INT64)
			p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_INT64);
		else if (vtype == G_TYPE_UINT64)
			p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_UINT64);
		else if (vtype == G_TYPE_STRING)
			p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_STRING);
		else if (vtype == G_TYPE_DOUBLE)
			p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_DOUBLE);
		else if (vtype == G_TYPE_STRV)
			p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_STRING_ARRAY);
		else if (vtype == G_TYPE_BYTES) {
			p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_BYTESTRING,
			                                              .gprop_to_dbus_fcn = _gprop_to_dbus_fcn_bytes);
		} else if (g_type_is_a (vtype, G_TYPE_ENUM)) {
			p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_INT32,
			                                              .gprop_to_dbus_fcn = _gprop_to_dbus_fcn_enum);
		} else if (g_type_is_a (vtype, G_TYPE_FLAGS)) {
			p->property_type = NM_SETT_INFO_PROPERT_TYPE (.dbus_type = G_VARIANT_TYPE_UINT32,
			                                              .gprop_to_dbus_fcn = _gprop_to_dbus_fcn_flags);
		} else
			nm_assert_not_reached ();

has_property_type:
		nm_assert (p->property_type);
		nm_assert (p->property_type->dbus_type);
		nm_assert (g_variant_type_string_is_valid ((const char *) p->property_type->dbus_type));
	}

	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMSettInfoProperty, name) == 0);
	g_array_sort (properties_override, nm_strcmp_p);

	setting_class->setting_info = &nm_meta_setting_infos[meta_type];
	sett_info->setting_class = setting_class;
	if (detail)
		sett_info->detail = *detail;
	nm_assert (properties_override->len > 0);
	sett_info->property_infos_len = properties_override->len;
	sett_info->property_infos = nm_memdup (properties_override->data, sizeof (NMSettInfoProperty) * properties_override->len);

	sett_info->property_infos_sorted = _property_infos_sort (sett_info->property_infos,
	                                                         sett_info->property_infos_len,
	                                                         setting_class);

	g_array_free (properties_override, TRUE);
}

const NMSettInfoProperty *
_nm_sett_info_setting_get_property_info (const NMSettInfoSetting *sett_info,
                                         const char *property_name)
{
	const NMSettInfoProperty *property;
	gssize idx;

	nm_assert (property_name);

	if (!sett_info)
		return NULL;

	G_STATIC_ASSERT_EXPR (G_STRUCT_OFFSET (NMSettInfoProperty, name) == 0);
	idx = nm_utils_array_find_binary_search (sett_info->property_infos,
	                                         sizeof (NMSettInfoProperty),
	                                         sett_info->property_infos_len,
	                                         &property_name,
	                                         nm_strcmp_p_with_data,
	                                         NULL);

	if (idx < 0)
		return NULL;

	property = &sett_info->property_infos[idx];

	nm_assert (idx == 0 || strcmp (property[-1].name, property[0].name) < 0);
	nm_assert (idx == sett_info->property_infos_len - 1 || strcmp (property[0].name, property[1].name) < 0);

	return property;
}

const NMSettInfoSetting *
_nm_setting_class_get_sett_info (NMSettingClass *setting_class)
{
	const NMSettInfoSetting *sett_info;

	if (   !NM_IS_SETTING_CLASS (setting_class)
	    || !setting_class->setting_info)
		return NULL;

	nm_assert (setting_class->setting_info->meta_type < G_N_ELEMENTS (_sett_info_settings));
	sett_info = &_sett_info_settings[setting_class->setting_info->meta_type];
	nm_assert (sett_info->setting_class == setting_class);
	return sett_info;
}

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

void
_nm_setting_emit_property_changed (NMSetting *setting)
{
	/* Some settings have "properties" that are not implemented as GObject properties.
	 *
	 * For example:
	 *
	 *   - gendata-base settings like NMSettingEthtool. Here properties are just
	 *     GVariant values in the gendata hash.
	 *
	 *   - NMSettingWireGuard's peers are not backed by a GObject property. Instead
	 *     there is C-API to access/modify peers.
	 *
	 * We still want to emit property-changed notifications for such properties,
	 * in particular because NMConnection registers to such signals to re-emit
	 * it as NM_CONNECTION_CHANGED signal. In fact, there are unlikely any other
	 * uses of such a property-changed signal, because generally it doesn't make
	 * too much sense.
	 *
	 * So, instead of adding yet another (artificial) signal "setting-changed",
	 * hijack the "notify" signal and just notify about changes of the "name".
	 * Of course, the "name" doesn't really ever change, because it's tied to
	 * the GObject's type.
	 */
	_notify (setting, PROP_NAME);
}

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

gboolean
_nm_setting_use_legacy_property (NMSetting *setting,
                                 GVariant *connection_dict,
                                 const char *legacy_property,
                                 const char *new_property)
{
	GVariant *setting_dict, *value;

	setting_dict = g_variant_lookup_value (connection_dict, nm_setting_get_name (NM_SETTING (setting)), NM_VARIANT_TYPE_SETTING);
	g_return_val_if_fail (setting_dict != NULL, FALSE);

	/* If the new property isn't set, we have to use the legacy property. */
	value = g_variant_lookup_value (setting_dict, new_property, NULL);
	if (!value) {
		g_variant_unref (setting_dict);
		return TRUE;
	}
	g_variant_unref (value);

	/* Otherwise, clients always prefer new properties sent from the daemon. */
	if (!_nm_utils_is_manager_process) {
		g_variant_unref (setting_dict);
		return FALSE;
	}

	/* The daemon prefers the legacy property if it exists. */
	value = g_variant_lookup_value (setting_dict, legacy_property, NULL);
	g_variant_unref (setting_dict);

	if (value) {
		g_variant_unref (value);
		return TRUE;
	} else
		return FALSE;
}

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

static GVariant *
property_to_dbus (const NMSettInfoSetting *sett_info,
                  guint property_idx,
                  NMConnection *connection,
                  NMSetting *setting,
                  NMConnectionSerializationFlags flags,
                  const NMConnectionSerializationOptions *options,
                  gboolean ignore_flags,
                  gboolean ignore_default)
{
	const NMSettInfoProperty *property = &sett_info->property_infos[property_idx];
	GVariant *variant;

	nm_assert (property->property_type->dbus_type);

	if (!property->param_spec) {
		if (!property->property_type->to_dbus_fcn)
			return NULL;
	} else if (   !ignore_flags
	           && !NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_TO_DBUS_IGNORE_FLAGS)) {
		if (!NM_FLAGS_HAS (property->param_spec->flags, G_PARAM_WRITABLE))
			return NULL;

		if (   NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_LEGACY)
		    && !_nm_utils_is_manager_process)
			return NULL;

		if (NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_SECRET)) {
			if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_NO_SECRETS))
				return NULL;

			if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_WITH_SECRETS_AGENT_OWNED)) {
				NMSettingSecretFlags f;

				/* see also _nm_connection_serialize_secrets() */
				if (!nm_setting_get_secret_flags (setting, property->param_spec->name, &f, NULL))
					return NULL;
				if (!NM_FLAGS_HAS (f, NM_SETTING_SECRET_FLAG_AGENT_OWNED))
					return NULL;
			}
		} else {
			if (NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS))
				return NULL;
		}
	}

	if (property->property_type->to_dbus_fcn) {
		variant = property->property_type->to_dbus_fcn (sett_info, property_idx, connection, setting, flags, options);
		nm_g_variant_take_ref (variant);
	} else {
		nm_auto_unset_gvalue GValue prop_value = { 0, };

		nm_assert (property->param_spec);

		g_value_init (&prop_value, property->param_spec->value_type);

		g_object_get_property (G_OBJECT (setting), property->param_spec->name, &prop_value);

		if (   ignore_default
		    && g_param_value_defaults (property->param_spec, &prop_value))
			return NULL;

		if (property->property_type->gprop_to_dbus_fcn) {
			variant = property->property_type->gprop_to_dbus_fcn (&prop_value);
			nm_g_variant_take_ref (variant);
		} else
			variant = g_dbus_gvalue_to_gvariant (&prop_value, property->property_type->dbus_type);
	}

	nm_assert (!variant || !g_variant_is_floating (variant));
	nm_assert (!variant || g_variant_is_of_type (variant, property->property_type->dbus_type));

	return variant;
}

static gboolean
set_property_from_dbus (const NMSettInfoProperty *property,
                        GVariant *src_value,
                        GValue *dst_value)
{
	nm_assert (property->param_spec);
	nm_assert (property->property_type->dbus_type);

	if (property->property_type->gprop_from_dbus_fcn) {
		if (!g_variant_type_equal (g_variant_get_type (src_value), property->property_type->dbus_type))
			return FALSE;
		property->property_type->gprop_from_dbus_fcn (src_value, dst_value);
	} else if (dst_value->g_type == G_TYPE_BYTES) {
		if (!g_variant_is_of_type (src_value, G_VARIANT_TYPE_BYTESTRING))
			return FALSE;

		_nm_utils_bytes_from_dbus (src_value, dst_value);
	} else {
		GValue tmp = G_VALUE_INIT;

		g_dbus_gvariant_to_gvalue (src_value, &tmp);
		if (G_VALUE_TYPE (&tmp) == G_VALUE_TYPE (dst_value))
			*dst_value = tmp;
		else {
			gboolean success;

			success = g_value_transform (&tmp, dst_value);
			g_value_unset (&tmp);
			if (!success)
				return FALSE;
		}
	}

	return TRUE;
}

/**
 * _nm_setting_to_dbus:
 * @setting: the #NMSetting
 * @connection: the #NMConnection containing @setting
 * @flags: hash flags, e.g. %NM_CONNECTION_SERIALIZE_ALL
 * @options: the #NMConnectionSerializationOptions options to control
 *   what/how gets serialized.
 *
 * Converts the #NMSetting into a #GVariant of type #NM_VARIANT_TYPE_SETTING
 * mapping each setting property name to a value describing that property,
 * suitable for marshalling over D-Bus or serializing.
 *
 * Returns: (transfer none): a new floating #GVariant describing the setting's
 * properties
 **/
GVariant *
_nm_setting_to_dbus (NMSetting *setting,
                     NMConnection *connection,
                     NMConnectionSerializationFlags flags,
                     const NMConnectionSerializationOptions *options)
{
	NMSettingPrivate *priv;
	GVariantBuilder builder;
	const NMSettInfoSetting *sett_info;
	guint n_properties, i;
	const char *const*gendata_keys;

	g_return_val_if_fail (NM_IS_SETTING (setting), NULL);

	priv = NM_SETTING_GET_PRIVATE (setting);

	g_variant_builder_init (&builder, NM_VARIANT_TYPE_SETTING);

	n_properties = _nm_setting_option_get_all (setting, &gendata_keys, NULL);
	for (i = 0; i < n_properties; i++) {
		g_variant_builder_add (&builder,
		                       "{sv}",
		                       gendata_keys[i],
		                       g_hash_table_lookup (priv->gendata->hash, gendata_keys[i]));
	}

	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
	for (i = 0; i < sett_info->property_infos_len; i++) {
		gs_unref_variant GVariant *dbus_value = NULL;

		dbus_value = property_to_dbus (sett_info, i, connection, setting, flags, options, FALSE, TRUE);
		if (dbus_value) {
			g_variant_builder_add (&builder,
			                       "{sv}",
			                       sett_info->property_infos[i].name,
			                       dbus_value);
		}
	}

	return g_variant_builder_end (&builder);
}

/**
 * _nm_setting_new_from_dbus:
 * @setting_type: the #NMSetting type which the hash contains properties for
 * @setting_dict: the #GVariant containing an %NM_VARIANT_TYPE_SETTING dictionary
 *   mapping property names to values
 * @connection_dict: the #GVariant containing an %NM_VARIANT_TYPE_CONNECTION
 *   dictionary mapping setting names to dictionaries.
 * @parse_flags: flags to determine behavior during parsing.
 * @error: location to store error, or %NULL
 *
 * Creates a new #NMSetting object and populates that object with the properties
 * contained in @setting_dict, using each key as the property to set, and each
 * value as the value to set that property to.  Setting properties are strongly
 * typed, thus the #GVariantType of the dict value must be correct.  See the
 * documentation on each #NMSetting object subclass for the correct property
 * names and value types.
 *
 * Returns: a new #NMSetting object populated with the properties from the
 * hash table, or %NULL if @setting_hash could not be deserialized.
 **/
NMSetting *
_nm_setting_new_from_dbus (GType setting_type,
                           GVariant *setting_dict,
                           GVariant *connection_dict,
                           NMSettingParseFlags parse_flags,
                           GError **error)
{
	gs_unref_ptrarray GPtrArray *keys_keep_variant = NULL;
	gs_unref_object NMSetting *setting = NULL;
	gs_unref_hashtable GHashTable *keys = NULL;

	g_return_val_if_fail (G_TYPE_IS_INSTANTIATABLE (setting_type), NULL);
	g_return_val_if_fail (g_variant_is_of_type (setting_dict, NM_VARIANT_TYPE_SETTING), NULL);

	nm_assert (!NM_FLAGS_ANY (parse_flags, ~NM_SETTING_PARSE_FLAGS_ALL));
	nm_assert (!NM_FLAGS_ALL (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT | NM_SETTING_PARSE_FLAGS_BEST_EFFORT));

	/* connection_dict is not technically optional, but some tests in test-general
	 * don't bother with it in cases where they know it's not needed.
	 */
	if (connection_dict)
		g_return_val_if_fail (g_variant_is_of_type (connection_dict, NM_VARIANT_TYPE_CONNECTION), NULL);

	/* Build the setting object from the properties we know about; we assume
	 * that any propreties in @setting_dict that we don't know about can
	 * either be ignored or else has a backward-compatibility equivalent
	 * that we do know about.
	 */
	setting = (NMSetting *) g_object_new (setting_type, NULL);

	if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
		GVariantIter iter;
		GVariant *entry, *entry_key;
		const char *key;

		keys_keep_variant = g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_unref);
		keys = g_hash_table_new (nm_str_hash, g_str_equal);

		g_variant_iter_init (&iter, setting_dict);
		while ((entry = g_variant_iter_next_value (&iter))) {
			entry_key = g_variant_get_child_value (entry, 0);
			g_ptr_array_add (keys_keep_variant, entry_key);
			g_variant_unref (entry);

			key = g_variant_get_string (entry_key, NULL);
			if (!g_hash_table_add (keys, (char *) key)) {
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
				             _("duplicate property"));
				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
				return NULL;
			}
		}
	}

	if (!NM_SETTING_GET_CLASS (setting)->init_from_dbus (setting,
	                                                     keys,
	                                                     setting_dict,
	                                                     connection_dict,
	                                                     parse_flags,
	                                                     error))
		return NULL;

	if (   NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)
	    && g_hash_table_size (keys) > 0) {
		GHashTableIter iter;
		const char *key;

		g_hash_table_iter_init (&iter, keys);
		if (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
			             _("unknown property"));
			g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
			return NULL;
		}
	}

	return g_steal_pointer (&setting);
}

static gboolean
init_from_dbus (NMSetting *setting,
                GHashTable *keys,
                GVariant *setting_dict,
                GVariant *connection_dict,
                guint /* NMSettingParseFlags */ parse_flags,
                GError **error)
{
	const NMSettInfoSetting *sett_info;

	guint i;

	nm_assert (NM_IS_SETTING (setting));
	nm_assert (!NM_FLAGS_ANY (parse_flags, ~NM_SETTING_PARSE_FLAGS_ALL));
	nm_assert (!NM_FLAGS_ALL (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT | NM_SETTING_PARSE_FLAGS_BEST_EFFORT));

	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));

	if (sett_info->detail.gendata_info) {
		GHashTable *hash;
		GVariantIter iter;
		char *key;
		GVariant *val;

		hash = _gendata_hash (setting, TRUE)->hash;

		g_variant_iter_init (&iter, setting_dict);
		while (g_variant_iter_next (&iter, "{sv}", &key, &val)) {
			g_hash_table_insert (hash,
			                     key,
			                     val);
			if (keys)
				g_hash_table_remove (keys, key);
		}

		_nm_setting_option_notify (setting, TRUE);

		/* Currently, only NMSettingEthtool supports gendata based options, and
		 * that one has no other properties (except "name"). That means, we
		 * consumed all options above.
		 *
		 * In the future it may be interesting to have settings that are both
		 * based on gendata and regular properties. In that case, we would need
		 * to handle this case differently. */
		nm_assert (nm_streq (G_OBJECT_TYPE_NAME (setting), "NMSettingEthtool"));
		nm_assert (sett_info->property_infos_len == 1);

		return TRUE;
	}

	for (i = 0; i < sett_info->property_infos_len; i++) {
		const NMSettInfoProperty *property_info = &sett_info->property_infos[i];
		gs_unref_variant GVariant *value = NULL;
		gs_free_error GError *local = NULL;

		if (   property_info->param_spec
		    && !(property_info->param_spec->flags & G_PARAM_WRITABLE))
			continue;

		value = g_variant_lookup_value (setting_dict, property_info->name, NULL);

		if (   value
		    && keys)
			g_hash_table_remove (keys, property_info->name);

		if (   value
		    && property_info->property_type->from_dbus_fcn) {

			if (!g_variant_type_equal (g_variant_get_type (value), property_info->property_type->dbus_type)) {
				/* for backward behavior, fail unless best-effort is chosen. */
				if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT))
					continue;
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				             _("can't set property of type '%s' from value of type '%s'"),
				             property_info->property_type->dbus_type ?
				                 g_variant_type_peek_string (property_info->property_type->dbus_type) :
				                 property_info->param_spec ?
				                     g_type_name (property_info->param_spec->value_type) : "(unknown)",
				             g_variant_get_type_string (value));
				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
				return FALSE;
			}

			if (!property_info->property_type->from_dbus_fcn (setting,
			                                                  connection_dict,
			                                                  property_info->name,
			                                                  value,
			                                                  parse_flags,
			                                                  &local)) {
				if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
					continue;
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				             _("failed to set property: %s"),
				             local->message);
				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
				return FALSE;
			}
		} else if (   !value
		           && property_info->property_type->missing_from_dbus_fcn) {
			if (!property_info->property_type->missing_from_dbus_fcn (setting,
			                                                          connection_dict,
			                                                          property_info->name,
			                                                          parse_flags,
			                                                          &local)) {
				if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
					continue;
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				             _("failed to set property: %s"),
				             local->message);
				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
				return FALSE;
			}
		} else if (   value
		           && property_info->param_spec) {
			nm_auto_unset_gvalue GValue object_value = G_VALUE_INIT;

			g_value_init (&object_value, property_info->param_spec->value_type);
			if (!set_property_from_dbus (property_info, value, &object_value)) {
				/* for backward behavior, fail unless best-effort is chosen. */
				if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_BEST_EFFORT))
					continue;
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				             _("can't set property of type '%s' from value of type '%s'"),
				               property_info->property_type->dbus_type
				             ? g_variant_type_peek_string (property_info->property_type->dbus_type)
				             : (  property_info->param_spec
				                ? g_type_name (property_info->param_spec->value_type)
				                : "(unknown)"),
				             g_variant_get_type_string (value));
				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
				return FALSE;
			}

			if (!nm_g_object_set_property (G_OBJECT (setting), property_info->param_spec->name, &object_value, &local)) {
				if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
					continue;
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				             _("can not set property: %s"),
				             local->message);
				g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), property_info->name);
				return FALSE;
			}
		}
	}

	return TRUE;
}

/**
 * nm_setting_get_dbus_property_type:
 * @setting: an #NMSetting
 * @property_name: the property of @setting to get the type of
 *
 * Gets the D-Bus marshalling type of a property. @property_name is a D-Bus
 * property name, which may not necessarily be a #GObject property.
 *
 * Returns: the D-Bus marshalling type of @property on @setting.
 */
const GVariantType *
nm_setting_get_dbus_property_type (NMSetting *setting,
                                   const char *property_name)
{
	const NMSettInfoProperty *property;

	g_return_val_if_fail (NM_IS_SETTING (setting), NULL);
	g_return_val_if_fail (property_name != NULL, NULL);

	property = _nm_setting_class_get_property_info (NM_SETTING_GET_CLASS (setting), property_name);

	g_return_val_if_fail (property != NULL, NULL);

	nm_assert (property->property_type);
	nm_assert (g_variant_type_string_is_valid ((const char *) property->property_type->dbus_type));

	return property->property_type->dbus_type;
}

gboolean
_nm_setting_get_property (NMSetting *setting, const char *property_name, GValue *value)
{
	const NMSettInfoSetting *sett_info;
	const NMSettInfoProperty *property_info;

	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
	g_return_val_if_fail (property_name, FALSE);
	g_return_val_if_fail (value, FALSE);

	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));

	if (sett_info->detail.gendata_info) {
		GVariant *variant;
		GenData *gendata = _gendata_hash (setting, FALSE);

		variant = gendata ? g_hash_table_lookup (gendata->hash, property_name) : NULL;

		if (!variant) {
			g_value_unset (value);
			return FALSE;
		}

		g_value_init (value, G_TYPE_VARIANT);
		g_value_set_variant (value, variant);
		return TRUE;
	}

	property_info = _nm_sett_info_setting_get_property_info (sett_info, property_name);
	if (   !property_info
	    || !property_info->param_spec) {
		g_value_unset (value);
		return FALSE;
	}

	g_value_init (value, property_info->param_spec->value_type);
	g_object_get_property (G_OBJECT (setting), property_name, value);
	return TRUE;
}

static void
_gobject_copy_property (GObject *src,
                        GObject *dst,
                        const char *property_name,
                        GType gtype)
{
	nm_auto_unset_gvalue GValue value = G_VALUE_INIT;

	nm_assert (G_IS_OBJECT (src));
	nm_assert (G_IS_OBJECT (dst));

	g_value_init (&value, gtype);
	g_object_get_property (src, property_name, &value);
	g_object_set_property (dst, property_name, &value);
}

static void
duplicate_copy_properties (const NMSettInfoSetting *sett_info,
                           NMSetting *src,
                           NMSetting *dst)
{
	if (sett_info->detail.gendata_info) {
		GenData *gendata = _gendata_hash (src, FALSE);

		nm_assert (!_gendata_hash (dst, FALSE));

		if (   gendata
		    && g_hash_table_size (gendata->hash) > 0) {
			GHashTableIter iter;
			GHashTable *h = _gendata_hash (dst, TRUE)->hash;
			const char *key;
			GVariant *val;

			g_hash_table_iter_init (&iter, gendata->hash);
			while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) {
				g_hash_table_insert (h,
				                     g_strdup (key),
				                     g_variant_ref (val));
			}
		}
	}

	if (sett_info->property_infos_len > 0) {
		gboolean frozen = FALSE;
		guint i;

		for (i = 0; i < sett_info->property_infos_len; i++) {
			const NMSettInfoProperty *property_info = &sett_info->property_infos[i];

			if (property_info->param_spec) {
				if ((property_info->param_spec->flags & (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)) != G_PARAM_WRITABLE)
					continue;

				if (!frozen) {
					g_object_freeze_notify (G_OBJECT (dst));
					frozen = TRUE;
				}
				_gobject_copy_property (G_OBJECT (src),
				                        G_OBJECT (dst),
				                        property_info->param_spec->name,
				                        G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec));
				continue;
			}
		}

		if (frozen)
			g_object_thaw_notify (G_OBJECT (dst));
	}
}

/**
 * nm_setting_duplicate:
 * @setting: the #NMSetting to duplicate
 *
 * Duplicates a #NMSetting.
 *
 * Returns: (transfer full): a new #NMSetting containing the same properties and values as the
 * source #NMSetting
 **/
NMSetting *
nm_setting_duplicate (NMSetting *setting)
{
	const NMSettInfoSetting *sett_info;
	NMSettingClass *klass;
	NMSetting *dst;

	g_return_val_if_fail (NM_IS_SETTING (setting), NULL);

	klass = NM_SETTING_GET_CLASS (setting);
	nm_assert (NM_IS_SETTING_CLASS (klass));
	nm_assert (klass->duplicate_copy_properties);

	dst = g_object_new (G_TYPE_FROM_CLASS (klass), NULL);

	sett_info = _nm_setting_class_get_sett_info (klass);
	nm_assert (sett_info);

	klass->duplicate_copy_properties (sett_info, setting, dst);
	return dst;
}

/**
 * nm_setting_get_name:
 * @setting: the #NMSetting
 *
 * Returns the type name of the #NMSetting object
 *
 * Returns: a string containing the type name of the #NMSetting object,
 * like 'ppp' or 'wireless' or 'wired'.
 **/
const char *
nm_setting_get_name (NMSetting *setting)
{
	const NMMetaSettingInfo *setting_info;

	g_return_val_if_fail (NM_IS_SETTING (setting), NULL);

	setting_info = NM_SETTING_GET_CLASS (setting)->setting_info;
	return setting_info ? setting_info->setting_name : NULL;
}

/**
 * nm_setting_verify:
 * @setting: the #NMSetting to verify
 * @connection: (allow-none): the #NMConnection that @setting came from, or
 *   %NULL if @setting is being verified in isolation.
 * @error: location to store error, or %NULL
 *
 * Validates the setting.  Each setting's properties have allowed values, and
 * some are dependent on other values (hence the need for @connection).  The
 * returned #GError contains information about which property of the setting
 * failed validation, and in what way that property failed validation.
 *
 * Returns: %TRUE if the setting is valid, %FALSE if it is not
 **/
gboolean
nm_setting_verify (NMSetting *setting, NMConnection *connection, GError **error)
{
	NMSettingVerifyResult result = _nm_setting_verify (setting, connection, error);

	if (result == NM_SETTING_VERIFY_NORMALIZABLE)
		g_clear_error (error);

	return result == NM_SETTING_VERIFY_SUCCESS || result == NM_SETTING_VERIFY_NORMALIZABLE;
}

NMSettingVerifyResult
_nm_setting_verify (NMSetting *setting, NMConnection *connection, GError **error)
{
	g_return_val_if_fail (NM_IS_SETTING (setting), NM_SETTING_VERIFY_ERROR);
	g_return_val_if_fail (!connection || NM_IS_CONNECTION (connection), NM_SETTING_VERIFY_ERROR);
	g_return_val_if_fail (!error || *error == NULL, NM_SETTING_VERIFY_ERROR);

	if (NM_SETTING_GET_CLASS (setting)->verify)
		return NM_SETTING_GET_CLASS (setting)->verify (setting, connection, error);

	return NM_SETTING_VERIFY_SUCCESS;
}

/**
 * nm_setting_verify_secrets:
 * @setting: the #NMSetting to verify secrets in
 * @connection: (allow-none): the #NMConnection that @setting came from, or
 *   %NULL if @setting is being verified in isolation.
 * @error: location to store error, or %NULL
 *
 * Verifies the secrets in the setting.
 * The returned #GError contains information about which secret of the setting
 * failed validation, and in what way that secret failed validation.
 * The secret validation is done separately from main setting validation, because
 * in some cases connection failure is not desired just for the secrets.
 *
 * Returns: %TRUE if the setting secrets are valid, %FALSE if they are not
 *
 * Since: 1.2
 **/
gboolean
nm_setting_verify_secrets (NMSetting *setting, NMConnection *connection, GError **error)
{
	g_return_val_if_fail (NM_IS_SETTING (setting), NM_SETTING_VERIFY_ERROR);
	g_return_val_if_fail (!connection || NM_IS_CONNECTION (connection), NM_SETTING_VERIFY_ERROR);
	g_return_val_if_fail (!error || *error == NULL, NM_SETTING_VERIFY_ERROR);

	if (NM_SETTING_GET_CLASS (setting)->verify_secrets)
		return NM_SETTING_GET_CLASS (setting)->verify_secrets (setting, connection, error);

	return NM_SETTING_VERIFY_SUCCESS;
}

gboolean
_nm_setting_verify_secret_string (const char *str,
                                  const char *setting_name,
                                  const char *property,
                                  GError **error)
{
	if (str && !*str) {
		g_set_error_literal (error,
		                     NM_CONNECTION_ERROR,
		                     NM_CONNECTION_ERROR_INVALID_PROPERTY,
		                     _("property is empty"));
		g_prefix_error (error, "%s.%s: ", setting_name, property);
		return FALSE;
	}
	return TRUE;
}

gboolean
_nm_setting_should_compare_secret_property (NMSetting *setting,
                                            NMSetting *other,
                                            const char *secret_name,
                                            NMSettingCompareFlags flags)
{
	NMSettingSecretFlags a_secret_flags = NM_SETTING_SECRET_FLAG_NONE;
	NMSettingSecretFlags b_secret_flags = NM_SETTING_SECRET_FLAG_NONE;

	nm_assert (NM_IS_SETTING (setting));
	nm_assert (!other || G_OBJECT_TYPE (setting) == G_OBJECT_TYPE (other));

	/* secret_name must be a valid secret for @setting. */
	nm_assert (nm_setting_get_secret_flags (setting, secret_name, NULL, NULL));

	if (!NM_FLAGS_ANY (flags,   NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS
	                          | NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS))
		return TRUE;

	nm_setting_get_secret_flags (setting, secret_name, &a_secret_flags, NULL);
	if (other) {
		if (!nm_setting_get_secret_flags (other, secret_name, &b_secret_flags, NULL)) {
			/* secret-name may not be a valid secret for @other. That is fine, we ignore that
			 * and treat @b_secret_flags as NM_SETTING_SECRET_FLAG_NONE.
			 *
			 * This can happen with VPN secrets, where the caller knows that @secret_name
			 * is a secret for setting, but it may not be a secret for @other. Accept that.
			 *
			 * Mark @other as missing. */
			other = NULL;
		}
	}

	/* when @setting has the secret-flags that should be ignored,
	 * we skip the comparison if:
	 *
	 *   - @other is not present,
	 *   - @other does not have a secret named @secret_name
	 *   - @other also has the secret flat to be ignored.
	 *
	 * This makes the check symmetric (aside the fact that @setting must
	 * have the secret while @other may not -- which is asymmetric). */
	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS)
	    && NM_FLAGS_HAS (a_secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)
	    && (   !other
	        || NM_FLAGS_HAS (b_secret_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)))
		return FALSE;

	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)
	    && NM_FLAGS_HAS (a_secret_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)
	    && (   !other
	        || NM_FLAGS_HAS (b_secret_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)))
		return FALSE;

	return TRUE;
}

static NMTernary
compare_property (const NMSettInfoSetting *sett_info,
                  guint property_idx,
                  NMConnection *con_a,
                  NMSetting *set_a,
                  NMConnection *con_b,
                  NMSetting *set_b,
                  NMSettingCompareFlags flags)
{
	const NMSettInfoProperty *property_info = &sett_info->property_infos[property_idx];
	const GParamSpec *param_spec = property_info->param_spec;

	if (!param_spec)
		return NM_TERNARY_DEFAULT;

	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_FUZZY)
	    && NM_FLAGS_ANY (param_spec->flags, NM_SETTING_PARAM_FUZZY_IGNORE | NM_SETTING_PARAM_SECRET))
		return NM_TERNARY_DEFAULT;

	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)
	    && !NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_INFERRABLE))
		return NM_TERNARY_DEFAULT;

	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_REAPPLY_IMMEDIATELY)
	    && NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_REAPPLY_IMMEDIATELY))
		return NM_TERNARY_DEFAULT;

	if (   NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)
	    && NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_SECRET))
		return NM_TERNARY_DEFAULT;

	if (nm_streq (param_spec->name, NM_SETTING_NAME))
		return NM_TERNARY_DEFAULT;

	if (   NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_SECRET)
	    && !_nm_setting_should_compare_secret_property (set_a,
	                                                    set_b,
	                                                    param_spec->name,
	                                                    flags))
		return NM_TERNARY_DEFAULT;

	if (set_b) {
		gs_unref_variant GVariant *value1  = NULL;
		gs_unref_variant GVariant *value2  = NULL;

		value1 = property_to_dbus (sett_info, property_idx, con_a, set_a, NM_CONNECTION_SERIALIZE_ALL, NULL, TRUE, TRUE);
		value2 = property_to_dbus (sett_info, property_idx, con_b, set_b, NM_CONNECTION_SERIALIZE_ALL, NULL, TRUE, TRUE);
		if (nm_property_compare (value1, value2) != 0)
			return NM_TERNARY_FALSE;
	}

	return NM_TERNARY_TRUE;
}

static NMTernary
_compare_property (const NMSettInfoSetting *sett_info,
                   guint property_idx,
                   NMConnection *con_a,
                   NMSetting *set_a,
                   NMConnection *con_b,
                   NMSetting *set_b,
                   NMSettingCompareFlags flags)
{
	NMTernary compare_result;

	nm_assert (sett_info);
	nm_assert (NM_IS_SETTING_CLASS (sett_info->setting_class));
	nm_assert (property_idx < sett_info->property_infos_len);
	nm_assert (NM_SETTING_GET_CLASS (set_a) == sett_info->setting_class);
	nm_assert (!set_b || NM_SETTING_GET_CLASS (set_b) == sett_info->setting_class);

	compare_result = NM_SETTING_GET_CLASS (set_a)->compare_property (sett_info,
	                                                                 property_idx,
	                                                                 con_a,
	                                                                 set_a,
	                                                                 con_b,
	                                                                 set_b,
	                                                                 flags);

	nm_assert (NM_IN_SET (compare_result, NM_TERNARY_DEFAULT,
	                                      NM_TERNARY_FALSE,
	                                      NM_TERNARY_TRUE));

	/* check that the inferable flag and the GObject property flag corresponds. */
	nm_assert (   !NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE)
	           || !sett_info->property_infos[property_idx].param_spec
	           || NM_FLAGS_HAS (sett_info->property_infos[property_idx].param_spec->flags, NM_SETTING_PARAM_INFERRABLE)
	           || compare_result == NM_TERNARY_DEFAULT);

#if NM_MORE_ASSERTS > 10
	/* assert that compare_property() is symeric. */
	nm_assert (   !set_b
	           || compare_result == NM_SETTING_GET_CLASS (set_a)->compare_property (sett_info,
	                                                                                property_idx,
	                                                                                con_b,
	                                                                                set_b,
	                                                                                con_a,
	                                                                                set_a,
	                                                                                flags));
#endif

	return compare_result;
}

/**
 * nm_setting_compare:
 * @a: a #NMSetting
 * @b: a second #NMSetting to compare with the first
 * @flags: compare flags, e.g. %NM_SETTING_COMPARE_FLAG_EXACT
 *
 * Compares two #NMSetting objects for similarity, with comparison behavior
 * modified by a set of flags.  See the documentation for #NMSettingCompareFlags
 * for a description of each flag's behavior.
 *
 * Returns: %TRUE if the comparison succeeds, %FALSE if it does not
 **/
gboolean
nm_setting_compare (NMSetting *a,
                    NMSetting *b,
                    NMSettingCompareFlags flags)
{
	return _nm_setting_compare (NULL, a, NULL, b, flags);
}

gboolean
_nm_setting_compare (NMConnection *con_a,
                     NMSetting *a,
                     NMConnection *con_b,
                     NMSetting *b,
                     NMSettingCompareFlags flags)
{
	const NMSettInfoSetting *sett_info;
	guint i;

	g_return_val_if_fail (NM_IS_SETTING (a), FALSE);
	g_return_val_if_fail (NM_IS_SETTING (b), FALSE);

	nm_assert (!con_a || NM_IS_CONNECTION (con_a));
	nm_assert (!con_b || NM_IS_CONNECTION (con_b));

	/* First check that both have the same type */
	if (G_OBJECT_TYPE (a) != G_OBJECT_TYPE (b))
		return FALSE;

	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (a));

	if (sett_info->detail.gendata_info) {
		GenData *a_gendata = _gendata_hash (a, FALSE);
		GenData *b_gendata = _gendata_hash (b, FALSE);

		return nm_utils_hash_table_equal (a_gendata ? a_gendata->hash : NULL,
		                                  b_gendata ? b_gendata->hash : NULL,
		                                  TRUE,
		                                  g_variant_equal);
	}

	for (i = 0; i < sett_info->property_infos_len; i++) {
		if (_compare_property (sett_info, i, con_a, a, con_b, b, flags) == NM_TERNARY_FALSE)
			return FALSE;
	}

	return TRUE;
}

static void
_setting_diff_add_result (GHashTable *results, const char *prop_name, NMSettingDiffResult r)
{
	void *p;

	if (r == NM_SETTING_DIFF_RESULT_UNKNOWN)
		return;

	if (g_hash_table_lookup_extended (results, prop_name, NULL, &p)) {
		if (!NM_FLAGS_ALL ((guint) r, GPOINTER_TO_UINT (p)))
			g_hash_table_insert (results, g_strdup (prop_name), GUINT_TO_POINTER (((guint) r) | GPOINTER_TO_UINT (p)));
	} else
		g_hash_table_insert (results, g_strdup (prop_name), GUINT_TO_POINTER (r));
}

/**
 * nm_setting_diff:
 * @a: a #NMSetting
 * @b: a second #NMSetting to compare with the first
 * @flags: compare flags, e.g. %NM_SETTING_COMPARE_FLAG_EXACT
 * @invert_results: this parameter is used internally by libnm and should
 * be set to %FALSE.  If %TRUE inverts the meaning of the #NMSettingDiffResult.
 * @results: (inout) (transfer full) (element-type utf8 guint32): if the
 * settings differ, on return a hash table mapping the differing keys to one or
 * more %NMSettingDiffResult values OR-ed together.  If the settings do not
 * differ, any hash table passed in is unmodified.  If no hash table is passed
 * in and the settings differ, a new one is created and returned.
 *
 * Compares two #NMSetting objects for similarity, with comparison behavior
 * modified by a set of flags.  See the documentation for #NMSettingCompareFlags
 * for a description of each flag's behavior.  If the settings differ, the keys
 * of each setting that differ from the other are added to @results, mapped to
 * one or more #NMSettingDiffResult values.
 *
 * Returns: %TRUE if the settings contain the same values, %FALSE if they do not
 **/
gboolean
nm_setting_diff (NMSetting *a,
                 NMSetting *b,
                 NMSettingCompareFlags flags,
                 gboolean invert_results,
                 GHashTable **results)
{
	return _nm_setting_diff (NULL, a, NULL, b, flags, invert_results, results);
}

gboolean
_nm_setting_diff (NMConnection *con_a,
                  NMSetting *a,
                  NMConnection *con_b,
                  NMSetting *b,
                  NMSettingCompareFlags flags,
                  gboolean invert_results,
                  GHashTable **results)
{
	const NMSettInfoSetting *sett_info;
	guint i;
	NMSettingDiffResult a_result = NM_SETTING_DIFF_RESULT_IN_A;
	NMSettingDiffResult b_result = NM_SETTING_DIFF_RESULT_IN_B;
	NMSettingDiffResult a_result_default = NM_SETTING_DIFF_RESULT_IN_A_DEFAULT;
	NMSettingDiffResult b_result_default = NM_SETTING_DIFF_RESULT_IN_B_DEFAULT;
	gboolean results_created = FALSE;
	gboolean compared_any = FALSE;
	gboolean diff_found = FALSE;

	g_return_val_if_fail (results != NULL, FALSE);
	g_return_val_if_fail (NM_IS_SETTING (a), FALSE);
	if (b) {
		g_return_val_if_fail (NM_IS_SETTING (b), FALSE);
		g_return_val_if_fail (G_OBJECT_TYPE (a) == G_OBJECT_TYPE (b), FALSE);
	}

	nm_assert (!con_a || NM_IS_CONNECTION (con_a));
	nm_assert (!con_b || NM_IS_CONNECTION (con_b));

	if ((flags & (NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) ==
	             (NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) {
		/* conflicting flags: default to WITH_DEFAULT (clearing NO_DEFAULT). */
		flags &= ~NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT;
	}

	/* If the caller is calling this function in a pattern like this to get
	 * complete diffs:
	 *
	 * nm_setting_diff (A, B, FALSE, &results);
	 * nm_setting_diff (B, A, TRUE, &results);
	 *
	 * and wants us to invert the results so that the second invocation comes
	 * out correctly, do that here.
	 */
	if (invert_results) {
		a_result = NM_SETTING_DIFF_RESULT_IN_B;
		b_result = NM_SETTING_DIFF_RESULT_IN_A;
		a_result_default = NM_SETTING_DIFF_RESULT_IN_B_DEFAULT;
		b_result_default = NM_SETTING_DIFF_RESULT_IN_A_DEFAULT;
	}

	if (*results == NULL) {
		*results = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, NULL);
		results_created = TRUE;
	}

	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (a));

	if (sett_info->detail.gendata_info) {
		const char *key;
		GVariant *val, *val2;
		GHashTableIter iter;
		GenData *a_gendata = _gendata_hash (a, FALSE);
		GenData *b_gendata = b ? _gendata_hash (b, FALSE) : NULL;

		if (!a_gendata || !b_gendata) {
			if (a_gendata || b_gendata) {
				NMSettingDiffResult one_sided_result;

				one_sided_result = a_gendata ? a_result : b_result;
				g_hash_table_iter_init (&iter, a_gendata ? a_gendata->hash : b_gendata->hash);
				while (g_hash_table_iter_next (&iter, (gpointer *) &key, NULL)) {
					diff_found = TRUE;
					_setting_diff_add_result (*results, key, one_sided_result);
				}
			}
		} else {
			g_hash_table_iter_init (&iter, a_gendata->hash);
			while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) {
				val2 = g_hash_table_lookup (b_gendata->hash, key);
				compared_any = TRUE;
				if (   !val2
				    || !g_variant_equal (val, val2)) {
					diff_found = TRUE;
					_setting_diff_add_result (*results, key, a_result);
				}
			}
			g_hash_table_iter_init (&iter, b_gendata->hash);
			while (g_hash_table_iter_next (&iter, (gpointer *) &key, (gpointer *) &val)) {
				val2 = g_hash_table_lookup (a_gendata->hash, key);
				compared_any = TRUE;
				if (   !val2
				    || !g_variant_equal (val, val2)) {
					diff_found = TRUE;
					_setting_diff_add_result (*results, key, b_result);
				}
			}
		}
	} else {
		for (i = 0; i < sett_info->property_infos_len; i++) {
			NMSettingDiffResult r = NM_SETTING_DIFF_RESULT_UNKNOWN;
			const NMSettInfoProperty *property_info;
			NMTernary compare_result;
			GParamSpec *prop_spec;

			compare_result = _compare_property (sett_info, i, con_a, a, con_b, b, flags);
			if (compare_result == NM_TERNARY_DEFAULT)
				continue;

			if (   NM_FLAGS_ANY (flags,   NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS
			                            | NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)
			    && b
			    && compare_result == NM_TERNARY_FALSE) {
				/* we have setting @b and the property is not the same. But we also are instructed
				 * to ignore secrets based on the flags.
				 *
				 * Note that compare_property() called with two settings will ignore secrets
				 * based on the flags, but it will do so if *both* settings have the flag we
				 * look for. So that is symmetric behavior and good.
				 *
				 * But for the purpose of diff(), we do a asymmetric comparison because and
				 * we want to skip testing the property if setting @a alone indicates to do
				 * so.
				 *
				 * We need to double-check whether the property should be ignored by
				 * looking at @a alone. */
				if (_compare_property (sett_info, i, con_a, a, NULL, NULL, flags) == NM_TERNARY_DEFAULT)
					continue;
			}

			compared_any = TRUE;

			property_info = &sett_info->property_infos[i];
			prop_spec = property_info->param_spec;

			if (b) {
				if (compare_result == NM_TERNARY_FALSE) {
					if (prop_spec) {
						gboolean a_is_default, b_is_default;
						GValue value = G_VALUE_INIT;

						g_value_init (&value, prop_spec->value_type);
						g_object_get_property (G_OBJECT (a), prop_spec->name, &value);
						a_is_default = g_param_value_defaults (prop_spec, &value);

						g_value_reset (&value);
						g_object_get_property (G_OBJECT (b), prop_spec->name, &value);
						b_is_default = g_param_value_defaults (prop_spec, &value);

						g_value_unset (&value);
						if (!NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT)) {
							if (!a_is_default)
								r |= a_result;
							if (!b_is_default)
								r |= b_result;
						} else {
							r |= a_result | b_result;
							if (a_is_default)
								r |= a_result_default;
							if (b_is_default)
								r |= b_result_default;
						}
					} else
						r |= a_result | b_result;
				}
			} else if ((flags & (NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT | NM_SETTING_COMPARE_FLAG_DIFF_RESULT_NO_DEFAULT)) == 0)
				r = a_result;  /* only in A */
			else {
				if (prop_spec) {
					GValue value = G_VALUE_INIT;

					g_value_init (&value, prop_spec->value_type);
					g_object_get_property (G_OBJECT (a), prop_spec->name, &value);
					if (!g_param_value_defaults (prop_spec, &value))
						r |= a_result;
					else if (flags & NM_SETTING_COMPARE_FLAG_DIFF_RESULT_WITH_DEFAULT)
						r |= a_result | a_result_default;

					g_value_unset (&value);
				} else
					r |= a_result;
			}

			if (r != NM_SETTING_DIFF_RESULT_UNKNOWN) {
				diff_found = TRUE;
				_setting_diff_add_result (*results, property_info->name, r);
			}
		}
	}

	if (!compared_any && !b) {
		/* special case: the setting has no properties, and the opposite
		 * setting @b is not given. The settings differ, and we signal that
		 * by returning an empty results hash. */
		diff_found = TRUE;
	}

	if (diff_found) {
		/* if there is a difference, we always return FALSE. It also means, we might
		 * have allocated a new @results hash, and return it to the caller. */
		return FALSE;
	} else {
		if (results_created) {
			/* the allocated hash is unused. Clear it again. */
			g_hash_table_destroy (*results);
			*results = NULL;
		} else {
			/* we found no diff, and return false. However, the input
			 * @result is returned unmodified. */
		}
		return TRUE;
	}
}

static void
enumerate_values (const NMSettInfoProperty *property_info,
                  NMSetting *setting,
                  NMSettingValueIterFn func,
                  gpointer user_data)
{
	GValue value = G_VALUE_INIT;

	if (!property_info->param_spec)
		return;

	g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (property_info->param_spec));
	g_object_get_property (G_OBJECT (setting), property_info->param_spec->name, &value);
	func (setting,
	      property_info->param_spec->name,
	      &value,
	      property_info->param_spec->flags,
	      user_data);
	g_value_unset (&value);
}

/**
 * nm_setting_enumerate_values:
 * @setting: the #NMSetting
 * @func: (scope call): user-supplied function called for each property of the setting
 * @user_data: user data passed to @func at each invocation
 *
 * Iterates over each property of the #NMSetting object, calling the supplied
 * user function for each property.
 **/
void
nm_setting_enumerate_values (NMSetting *setting,
                             NMSettingValueIterFn func,
                             gpointer user_data)
{
	const NMSettInfoSetting *sett_info;
	guint i;

	g_return_if_fail (NM_IS_SETTING (setting));
	g_return_if_fail (func != NULL);

	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));

	if (sett_info->detail.gendata_info) {
		const char *const*names;
		guint n_properties;

		/* the properties of this setting are not real GObject properties.
		 * Hence, this API makes little sense (or does it?). Still, call
		 * @func with each value. */
		n_properties = _nm_setting_option_get_all (setting, &names, NULL);
		if (n_properties > 0) {
			gs_strfreev char **keys = g_strdupv ((char **) names);
			GHashTable *h = _gendata_hash (setting, FALSE)->hash;

			for (i = 0; i < n_properties; i++) {
				GValue value = G_VALUE_INIT;
				GVariant *val = g_hash_table_lookup (h, keys[i]);

				if (!val) {
					/* was deleted in the meantime? Skip */
					continue;
				}

				g_value_init (&value, G_TYPE_VARIANT);
				g_value_set_variant (&value, val);
				/* call it will GParamFlags 0. It shall indicate that this
				 * is not a "real" GObject property. */
				func (setting, keys[i], &value, 0, user_data);
				g_value_unset (&value);
			}
		}
		return;
	}

	for (i = 0; i < sett_info->property_infos_len; i++) {
		NM_SETTING_GET_CLASS (setting)->enumerate_values (_nm_sett_info_property_info_get_sorted (sett_info, i),
		                                                  setting,
		                                                  func,
		                                                  user_data);
	}
}

static gboolean
aggregate (NMSetting *setting,
           int type_i,
           gpointer arg)
{
	NMConnectionAggregateType type = type_i;
	const NMSettInfoSetting *sett_info;
	guint i;

	nm_assert (NM_IN_SET (type, NM_CONNECTION_AGGREGATE_ANY_SECRETS,
	                            NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS));

	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
	for (i = 0; i < sett_info->property_infos_len; i++) {
		const NMSettInfoProperty *property_info = &sett_info->property_infos[i];
		GParamSpec *prop_spec = property_info->param_spec;
		nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
		NMSettingSecretFlags secret_flags;

		if (   !prop_spec
		    || !NM_FLAGS_HAS (prop_spec->flags, NM_SETTING_PARAM_SECRET)) {
			nm_assert (!nm_setting_get_secret_flags (setting, property_info->name, NULL, NULL));
			continue;
		}

		/* for the moment, all aggregate types only care about secrets. */
		nm_assert (nm_setting_get_secret_flags (setting, property_info->name, NULL, NULL));

		switch (type) {

		case NM_CONNECTION_AGGREGATE_ANY_SECRETS:
			g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (prop_spec));
			g_object_get_property (G_OBJECT (setting), prop_spec->name, &value);
			if (!g_param_value_defaults (prop_spec, &value)) {
				*((gboolean *) arg) = TRUE;
				return TRUE;
			}
			break;

		case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS:
			if (!nm_setting_get_secret_flags (setting, prop_spec->name, &secret_flags, NULL))
				nm_assert_not_reached ();
			if (secret_flags == NM_SETTING_SECRET_FLAG_NONE) {
				*((gboolean *) arg) = TRUE;
				return TRUE;
			}
			break;

		}
	}

	return FALSE;
}

/**
 * _nm_setting_aggregate:
 * @setting: the #NMSetting to aggregate.
 * @type: the #NMConnectionAggregateType aggregate type.
 * @arg: the in/out arguments for aggregation. They depend on @type.
 *
 * This is the implementation detail of _nm_connection_aggregate(). It
 * makes no sense to call this function directly outside of _nm_connection_aggregate().
 *
 * Returns: %TRUE if afterwards the aggregation is complete. That means,
 *   the only caller _nm_connection_aggregate() will not visit other settings
 *   after a setting returns %TRUE (indicating that there is nothing further
 *   to aggregate). Note that is very different from the boolean return
 *   argument of _nm_connection_aggregate(), which serves a different purpose.
 */
gboolean
_nm_setting_aggregate (NMSetting *setting,
                       NMConnectionAggregateType type,
                       gpointer arg)
{
	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
	g_return_val_if_fail (arg, FALSE);
	g_return_val_if_fail (NM_IN_SET (type, NM_CONNECTION_AGGREGATE_ANY_SECRETS,
	                                       NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS),
	                      FALSE);

	return NM_SETTING_GET_CLASS (setting)->aggregate (setting, type, arg);
}

static gboolean
clear_secrets (const NMSettInfoSetting *sett_info,
               guint property_idx,
               NMSetting *setting,
               NMSettingClearSecretsWithFlagsFn func,
               gpointer user_data)
{
	NMSettingSecretFlags flags = NM_SETTING_SECRET_FLAG_NONE;
	GParamSpec *param_spec = sett_info->property_infos[property_idx].param_spec;

	if (!param_spec)
		return FALSE;

	if (!NM_FLAGS_HAS (param_spec->flags, NM_SETTING_PARAM_SECRET))
		return FALSE;

	if (func) {
		if (!nm_setting_get_secret_flags (setting, param_spec->name, &flags, NULL))
			nm_assert_not_reached ();
		if (!func (setting, param_spec->name, flags, user_data))
			return FALSE;
	} else
		nm_assert (nm_setting_get_secret_flags (setting, param_spec->name, NULL, NULL));

	{
		nm_auto_unset_gvalue GValue value = G_VALUE_INIT;

		g_value_init (&value, param_spec->value_type);
		g_object_get_property (G_OBJECT (setting), param_spec->name, &value);
		if (g_param_value_defaults (param_spec, &value))
			return FALSE;

		g_param_value_set_default (param_spec, &value);
		g_object_set_property (G_OBJECT (setting), param_spec->name, &value);
	}

	return TRUE;
}

/**
 * _nm_setting_clear_secrets:
 * @setting: the #NMSetting
 * @func: (scope call): function to be called to determine whether a
 *     specific secret should be cleared or not
 * @user_data: caller-supplied data passed to @func
 *
 * Clears and frees secrets determined by @func.
 *
 * Returns: %TRUE if the setting changed at all
 **/
gboolean
_nm_setting_clear_secrets (NMSetting *setting,
                           NMSettingClearSecretsWithFlagsFn func,
                           gpointer user_data)
{
	const NMSettInfoSetting *sett_info;
	gboolean changed = FALSE;
	guint i;
	gboolean (*my_clear_secrets) (const struct _NMSettInfoSetting *sett_info,
	                              guint property_idx,
	                              NMSetting *setting,
	                              NMSettingClearSecretsWithFlagsFn func,
	                              gpointer user_data);

	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);

	my_clear_secrets = NM_SETTING_GET_CLASS (setting)->clear_secrets;

	sett_info = _nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting));
	for (i = 0; i < sett_info->property_infos_len; i++) {
		changed |= my_clear_secrets (sett_info,
		                             i,
		                             setting,
		                             func,
		                             user_data);
	}
	return changed;
}

/**
 * _nm_setting_need_secrets:
 * @setting: the #NMSetting
 *
 * Returns an array of property names for each secret which may be required
 * to make a successful connection.  The returned hints are only intended as a
 * guide to what secrets may be required, because in some circumstances, there
 * is no way to conclusively determine exactly which secrets are needed.
 *
 * Returns: (transfer container) (element-type utf8): a #GPtrArray containing
 * the property names of secrets of the #NMSetting which may be required; the
 * caller owns the array and must free it with g_ptr_array_free(), but must not
 * free the elements.
 **/
GPtrArray *
_nm_setting_need_secrets (NMSetting *setting)
{
	GPtrArray *secrets = NULL;

	g_return_val_if_fail (NM_IS_SETTING (setting), NULL);

	if (NM_SETTING_GET_CLASS (setting)->need_secrets)
		secrets = NM_SETTING_GET_CLASS (setting)->need_secrets (setting);

	return secrets;
}

static int
update_one_secret (NMSetting *setting, const char *key, GVariant *value, GError **error)
{
	const NMSettInfoProperty *property;
	GParamSpec *prop_spec;
	GValue prop_value = { 0, };

	property = _nm_setting_class_get_property_info (NM_SETTING_GET_CLASS (setting), key);
	if (!property) {
		g_set_error_literal (error,
		                     NM_CONNECTION_ERROR,
		                     NM_CONNECTION_ERROR_PROPERTY_NOT_FOUND,
		                     _("secret not found"));
		g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), key);
		return NM_SETTING_UPDATE_SECRET_ERROR;
	}

	/* Silently ignore non-secrets */
	prop_spec = property->param_spec;
	if (!prop_spec || !(prop_spec->flags & NM_SETTING_PARAM_SECRET))
		return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;

	if (   g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)
	    && G_IS_PARAM_SPEC_STRING (prop_spec)) {
		/* String is expected to be a common case. Handle it specially and check
		 * whether the value is already set. Otherwise, we just reset the
		 * property and assume the value got modified.
		 */
		char *v;

		g_object_get (G_OBJECT (setting), prop_spec->name, &v, NULL);
		if (g_strcmp0 (v, g_variant_get_string (value, NULL)) == 0) {
			g_free (v);
			return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;
		}
		g_free (v);
	}

	g_value_init (&prop_value, prop_spec->value_type);
	set_property_from_dbus (property, value, &prop_value);
	g_object_set_property (G_OBJECT (setting), prop_spec->name, &prop_value);
	g_value_unset (&prop_value);

	return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
}

/**
 * _nm_setting_update_secrets:
 * @setting: the #NMSetting
 * @secrets: a #GVariant of type #NM_VARIANT_TYPE_SETTING, mapping property
 *   names to secrets.
 * @error: location to store error, or %NULL
 *
 * Update the setting's secrets, given a dictionary of secrets intended for that
 * setting (deserialized from D-Bus for example).
 *
 * Returns: an #NMSettingUpdateSecretResult
 **/
NMSettingUpdateSecretResult
_nm_setting_update_secrets (NMSetting *setting, GVariant *secrets, GError **error)
{
	GVariantIter iter;
	const char *secret_key;
	GVariant *secret_value;
	GError *tmp_error = NULL;
	NMSettingUpdateSecretResult result = NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;

	g_return_val_if_fail (NM_IS_SETTING (setting), NM_SETTING_UPDATE_SECRET_ERROR);
	g_return_val_if_fail (g_variant_is_of_type (secrets, NM_VARIANT_TYPE_SETTING), NM_SETTING_UPDATE_SECRET_ERROR);
	if (error)
		g_return_val_if_fail (*error == NULL, NM_SETTING_UPDATE_SECRET_ERROR);

	g_variant_iter_init (&iter, secrets);
	while (g_variant_iter_next (&iter, "{&sv}", &secret_key, &secret_value)) {
		int success;

		success = NM_SETTING_GET_CLASS (setting)->update_one_secret (setting, secret_key, secret_value, &tmp_error);
		nm_assert (!((success == NM_SETTING_UPDATE_SECRET_ERROR) ^ (!!tmp_error)));

		g_variant_unref (secret_value);

		if (success == NM_SETTING_UPDATE_SECRET_ERROR) {
			g_propagate_error (error, tmp_error);
			return NM_SETTING_UPDATE_SECRET_ERROR;
		}

		if (success == NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED)
			result = NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
	}

	return result;
}

static void
for_each_secret (NMSetting *setting,
                 const char *secret_name,
                 GVariant *val,
                 gboolean remove_non_secrets,
                 _NMConnectionForEachSecretFunc callback,
                 gpointer callback_data,
                 GVariantBuilder *setting_builder)
{
	NMSettingSecretFlags secret_flags = NM_SETTING_SECRET_FLAG_NONE;

	if (!nm_setting_get_secret_flags (setting, secret_name, &secret_flags, NULL)) {
		if (!remove_non_secrets)
			g_variant_builder_add (setting_builder, "{sv}", secret_name, val);
		return;
	}
	if (callback (secret_flags, callback_data))
		g_variant_builder_add (setting_builder, "{sv}", secret_name, val);
}

static void
_set_error_secret_property_not_found (GError **error,
                                      NMSetting *setting,
                                      const char *secret_name)
{
	g_set_error_literal (error,
	                     NM_CONNECTION_ERROR,
	                     NM_CONNECTION_ERROR_PROPERTY_NOT_FOUND,
	                     _("not a secret property"));
	g_prefix_error (error, "%s.%s: ", nm_setting_get_name (setting), secret_name);
}

gboolean
_nm_setting_property_is_regular_secret (NMSetting *setting,
                                        const char *secret_name)
{
	const NMSettInfoProperty *property;

	nm_assert (NM_IS_SETTING (setting));
	nm_assert (secret_name);

	property = _nm_setting_class_get_property_info (NM_SETTING_GET_CLASS (setting), secret_name);
	return    property
	       && property->param_spec
	       && NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_SECRET);
}

gboolean
_nm_setting_property_is_regular_secret_flags (NMSetting *setting,
                                              const char *secret_flags_name)
{
	const NMSettInfoProperty *property;

	nm_assert (NM_IS_SETTING (setting));
	nm_assert (secret_flags_name);

	property = _nm_setting_class_get_property_info (NM_SETTING_GET_CLASS (setting), secret_flags_name);
	return    property
	       && property->param_spec
	       && !NM_FLAGS_HAS (property->param_spec->flags, NM_SETTING_PARAM_SECRET)
	       && G_PARAM_SPEC_VALUE_TYPE (property->param_spec) == NM_TYPE_SETTING_SECRET_FLAGS;
}

static gboolean
get_secret_flags (NMSetting *setting,
                  const char *secret_name,
                  NMSettingSecretFlags *out_flags,
                  GError **error)
{
	gs_free char *secret_flags_name_free = NULL;
	const char *secret_flags_name;
	NMSettingSecretFlags flags;

	if (!_nm_setting_property_is_regular_secret (setting,
	                                             secret_name)) {
		_set_error_secret_property_not_found (error, setting, secret_name);
		NM_SET_OUT (out_flags, NM_SETTING_SECRET_FLAG_NONE);
		return FALSE;
	}

	secret_flags_name = nm_construct_name_a ("%s-flags", secret_name, &secret_flags_name_free);

	nm_assert (_nm_setting_property_is_regular_secret_flags (setting, secret_flags_name));

	g_object_get (G_OBJECT (setting),
	              secret_flags_name,
	              &flags,
	              NULL);
	NM_SET_OUT (out_flags, flags);
	return TRUE;
}

/**
 * nm_setting_get_secret_flags:
 * @setting: the #NMSetting
 * @secret_name: the secret key name to get flags for
 * @out_flags: on success, the #NMSettingSecretFlags for the secret
 * @error: location to store error, or %NULL
 *
 * For a given secret, retrieves the #NMSettingSecretFlags describing how to
 * handle that secret.
 *
 * Returns: %TRUE on success (if the given secret name was a valid property of
 * this setting, and if that property is secret), %FALSE if not
 **/
gboolean
nm_setting_get_secret_flags (NMSetting *setting,
                             const char *secret_name,
                             NMSettingSecretFlags *out_flags,
                             GError **error)
{
	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
	g_return_val_if_fail (secret_name != NULL, FALSE);

	return NM_SETTING_GET_CLASS (setting)->get_secret_flags (setting, secret_name, out_flags, error);
}

static gboolean
set_secret_flags (NMSetting *setting,
                  const char *secret_name,
                  NMSettingSecretFlags flags,
                  GError **error)
{
	gs_free char *secret_flags_name_free = NULL;
	const char *secret_flags_name;

	if (!_nm_setting_property_is_regular_secret (setting,
	                                             secret_name)) {
		_set_error_secret_property_not_found (error, setting, secret_name);
		return FALSE;
	}

	secret_flags_name = nm_construct_name_a ("%s-flags", secret_name, &secret_flags_name_free);

	nm_assert (_nm_setting_property_is_regular_secret_flags (setting, secret_flags_name));

	if (!nm_g_object_set_property_flags (G_OBJECT (setting),
	                                     secret_flags_name,
	                                     NM_TYPE_SETTING_SECRET_FLAGS,
	                                     flags,
	                                     error))
		g_return_val_if_reached (FALSE);
	return TRUE;
}

/**
 * nm_setting_set_secret_flags:
 * @setting: the #NMSetting
 * @secret_name: the secret key name to set flags for
 * @flags: the #NMSettingSecretFlags for the secret
 * @error: location to store error, or %NULL
 *
 * For a given secret, stores the #NMSettingSecretFlags describing how to
 * handle that secret.
 *
 * Returns: %TRUE on success (if the given secret name was a valid property of
 * this setting, and if that property is secret), %FALSE if not
 **/
gboolean
nm_setting_set_secret_flags (NMSetting *setting,
                             const char *secret_name,
                             NMSettingSecretFlags flags,
                             GError **error)
{
	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
	g_return_val_if_fail (secret_name != NULL, FALSE);
	g_return_val_if_fail (_nm_setting_secret_flags_valid (flags), FALSE);

	return NM_SETTING_GET_CLASS (setting)->set_secret_flags (setting, secret_name, flags, error);
}

/**
 * nm_setting_to_string:
 * @setting: the #NMSetting
 *
 * Convert the setting (including secrets!) into a string. For debugging
 * purposes ONLY, should NOT be used for serialization of the setting,
 * or machine-parsed in any way. The output format is not guaranteed to
 * be stable and may change at any time.
 *
 * Returns: an allocated string containing a textual representation of the
 * setting's properties and values, which the caller should
 * free with g_free()
 **/
char *
nm_setting_to_string (NMSetting *setting)
{
	GString *string;
	gs_unref_variant GVariant *variant = NULL;
	GVariant *child;
	GVariantIter iter;

	string = g_string_new (nm_setting_get_name (setting));
	g_string_append_c (string, '\n');

	variant = _nm_setting_to_dbus (setting, NULL, NM_CONNECTION_SERIALIZE_ALL, NULL);

	g_variant_iter_init (&iter, variant);
	while ((child = g_variant_iter_next_value (&iter))) {
		gs_free char *name = NULL;
		gs_free char *value_str = NULL;
		gs_unref_variant GVariant *value = NULL;

		g_variant_get (child, "{sv}", &name, &value);
		value_str = g_variant_print (value, FALSE);

		g_string_append_printf (string, "\t%s : %s\n", name, value_str);
	}

	return g_string_free (string, FALSE);
}

static GVariant *
_nm_setting_get_deprecated_virtual_interface_name (const NMSettInfoSetting *sett_info,
                                                   guint property_idx,
                                                   NMConnection *connection,
                                                   NMSetting *setting,
                                                   NMConnectionSerializationFlags flags,
                                                   const NMConnectionSerializationOptions *options)
{
	NMSettingConnection *s_con;

	if (!connection)
		return NULL;

	s_con = nm_connection_get_setting_connection (connection);
	if (!s_con)
		return NULL;

	if (nm_setting_connection_get_interface_name (s_con))
		return g_variant_new_string (nm_setting_connection_get_interface_name (s_con));
	else
		return NULL;
}

const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_interface_name = {
	.dbus_type   = G_VARIANT_TYPE_STRING,
	.to_dbus_fcn = _nm_setting_get_deprecated_virtual_interface_name,
};

const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_i = {
	.dbus_type   = G_VARIANT_TYPE_INT32,
	/* No functions set. This property type is to silently ignore the value on D-Bus. */
};

const NMSettInfoPropertType nm_sett_info_propert_type_deprecated_ignore_u = {
	.dbus_type   = G_VARIANT_TYPE_UINT32,
	/* No functions set. This property type is to silently ignore the value on D-Bus. */
};

const NMSettInfoPropertType nm_sett_info_propert_type_plain_i = {
	.dbus_type   = G_VARIANT_TYPE_INT32,
};

const NMSettInfoPropertType nm_sett_info_propert_type_plain_u = {
	.dbus_type   = G_VARIANT_TYPE_UINT32,
};

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

static GenData *
_gendata_hash (NMSetting *setting, gboolean create_if_necessary)
{
	NMSettingPrivate *priv;

	nm_assert (NM_IS_SETTING (setting));

	priv = NM_SETTING_GET_PRIVATE (setting);

	if (G_UNLIKELY (!priv->gendata)) {
		if (!create_if_necessary)
			return NULL;
		priv->gendata = g_slice_new (GenData);
		priv->gendata->hash = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, (GDestroyNotify) g_variant_unref);
		priv->gendata->names = NULL;
		priv->gendata->values = NULL;
	}

	return priv->gendata;
}

GHashTable *
_nm_setting_option_hash (NMSetting *setting, gboolean create_if_necessary)
{
	GenData *gendata;

	gendata = _gendata_hash (setting, create_if_necessary);
	return gendata ? gendata->hash : NULL;
}

void
_nm_setting_option_notify (NMSetting *setting,
                           gboolean names_changed)
{
	GenData *gendata;

	gendata = _gendata_hash (setting, FALSE);
	if (!gendata)
		goto out;

	nm_clear_g_free (&gendata->values);

	if (names_changed) {
		/* if only the values changed, it's sufficient to invalidate the
		 * values cache. Otherwise, the names cache must be invalidated too. */
		nm_clear_g_free (&gendata->names);
	}

	/* Note, currently there is no way to notify the subclass when gendata changed.
	 * gendata is only changed in two situations:
	 *   1) from within NMSetting itself, for example when creating a NMSetting instance
	 *      from keyfile or a D-Bus GVariant.
	 *   2) actively from the subclass itself
	 * For 2), we don't need the notification, because the subclass knows that something
	 * changed.
	 * For 1), we currently don't need the notification either, because all that the subclass
	 * currently would do, is emit a g_object_notify() signal. However, 1) only happens when
	 * the setting instance is newly created, at that point, nobody listens to the signal.
	 *
	 * If we ever need it, then we would need to call a virtual function to notify the subclass
	 * that gendata changed. */

out:
	_nm_setting_emit_property_changed (setting);
}

guint
_nm_setting_option_get_all (NMSetting *setting,
                            const char *const**out_names,
                            GVariant *const**out_values)
{
	GenData *gendata;
	GHashTable *hash;
	guint i, len;

	nm_assert (NM_IS_SETTING (setting));

	gendata = _gendata_hash (setting, FALSE);
	if (!gendata)
		goto out_zero;

	hash = gendata->hash;
	len = g_hash_table_size (hash);
	if (len == 0)
		goto out_zero;

	if (!out_names && !out_values)
		return len;

	if (G_UNLIKELY (!gendata->names)) {
		gendata->names = nm_utils_strdict_get_keys (hash,
		                                            TRUE,
		                                            NULL);
	}

	if (out_values) {
		if (G_UNLIKELY (!gendata->values)) {
			gendata->values = g_new (GVariant *, len + 1);
			for (i = 0; i < len; i++)
				gendata->values[i] = g_hash_table_lookup (hash, gendata->names[i]);
			gendata->values[i] = NULL;
		}
		*out_values = gendata->values;
	}

	NM_SET_OUT (out_names, (const char *const*) gendata->names);
	return len;

out_zero:
	NM_SET_OUT (out_names, NULL);
	NM_SET_OUT (out_values, NULL);
	return 0;
}

/**
 * nm_setting_option_get_all_names:
 * @setting: the #NMSetting
 * @out_len: (allow-none) (out):
 *
 * Gives the name of all set options.
 *
 * Returns: (array length=out_len zero-terminated=1) (transfer none):
 *   A %NULL terminated array of key names. If no names are present, this returns
 *   %NULL. The returned array and the names are owned by %NMSetting and might be invalidated
 *   by the next operation.
 *
 * Since: 1.26
 **/
const char *const*
nm_setting_option_get_all_names (NMSetting *setting,
                                 guint *out_len)
{
	const char *const*names;
	guint len;

	g_return_val_if_fail (NM_IS_SETTING (setting), NULL);

	len = _nm_setting_option_get_all (setting, &names, NULL);
	NM_SET_OUT (out_len, len);
	return names;
}

gboolean
_nm_setting_option_clear (NMSetting *setting,
                          const char *optname)
{
	GHashTable *ht;

	nm_assert (NM_IS_SETTING (setting));
	nm_assert (nm_str_not_empty (optname));

	ht = _nm_setting_option_hash (setting, FALSE);
	if (!ht)
		return FALSE;

	return g_hash_table_remove (ht, optname);
}

/**
 * nm_setting_option_clear_by_name:
 * @setting: the #NMSetting
 * @predicate: (allow-none) (scope call): the predicate for which names
 *   should be clear.
 *   If the predicate returns %TRUE for an option name, the option
 *   gets removed. If %NULL, all options will be removed.
 *
 * Since: 1.26
 */
void
nm_setting_option_clear_by_name (NMSetting *setting,
                                 NMUtilsPredicateStr predicate)
{
	GHashTable *hash;
	GHashTableIter iter;
	const char *name;
	gboolean changed = FALSE;

	g_return_if_fail (NM_IS_SETTING (setting));

	hash = _nm_setting_option_hash (NM_SETTING (setting), FALSE);
	if (!hash)
		return;

	if (!predicate) {
		changed = (g_hash_table_size (hash) > 0);
		if (changed)
			g_hash_table_remove_all (hash);
	} else {
		g_hash_table_iter_init (&iter, hash);
		while (g_hash_table_iter_next (&iter, (gpointer *) &name, NULL)) {
			if (predicate (name)) {
				g_hash_table_iter_remove (&iter);
				changed = TRUE;
			}
		}
	}

	if (changed)
		_nm_setting_option_notify (setting, TRUE);
}

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

/**
 * nm_setting_option_get:
 * @setting: the #NMSetting
 * @opt_name: the option name to request.
 *
 * Returns: (transfer none): the #GVariant or %NULL if the option
 *   is not set.
 *
 * Since: 1.26.
 */
GVariant *
nm_setting_option_get (NMSetting *setting,
                      const char *opt_name)
{
	GenData *gendata;

	g_return_val_if_fail (NM_IS_SETTING (setting), FALSE);
	g_return_val_if_fail (opt_name, FALSE);

	gendata = _gendata_hash (setting, FALSE);
	return gendata ? g_hash_table_lookup (gendata->hash, opt_name) : NULL;
}

/**
 * nm_setting_option_get_boolean:
 * @setting: the #NMSetting
 * @opt_name: the option to get
 * @out_value: (allow-none) (out): the optional output value.
 *   If the option is unset, %FALSE will be returned.
 *
 * Returns: %TRUE if @opt_name is set to a boolean variant.
 *
 * Since: 1.26
 */
gboolean
nm_setting_option_get_boolean (NMSetting *setting,
                               const char *opt_name,
                               gboolean *out_value)
{
	GVariant *v;

	v = nm_setting_option_get (NM_SETTING (setting), opt_name);
	if (   v
	    && g_variant_is_of_type (v, G_VARIANT_TYPE_BOOLEAN)) {
		NM_SET_OUT (out_value, g_variant_get_boolean (v));
		return TRUE;
	}
	NM_SET_OUT (out_value, FALSE);
	return FALSE;
}

/**
 * nm_setting_option_get_uint32:
 * @setting: the #NMSetting
 * @opt_name: the option to get
 * @out_value: (allow-none) (out): the optional output value.
 *   If the option is unset, 0 will be returned.
 *
 * Returns: %TRUE if @opt_name is set to a uint32 variant.
 *
 * Since: 1.26
 */
gboolean
nm_setting_option_get_uint32 (NMSetting *setting,
                              const char *opt_name,
                              guint32 *out_value)
{
	GVariant *v;

	v = nm_setting_option_get (NM_SETTING (setting), opt_name);
	if (   v
	    && g_variant_is_of_type (v, G_VARIANT_TYPE_UINT32)) {
		NM_SET_OUT (out_value, g_variant_get_uint32 (v));
		return TRUE;
	}
	NM_SET_OUT (out_value, 0);
	return FALSE;
}

/**
 * nm_setting_option_set:
 * @setting: the #NMSetting
 * @opt_name: the option name to set
 * @variant: (allow-none): the variant to set.
 *
 * If @variant is %NULL, this clears the option if it is set.
 * Otherwise, @variant is set as the option. If @variant is
 * a floating reference, it will be consumed.
 *
 * Note that not all setting types support options. It is a bug
 * setting a variant to a setting that doesn't support it.
 * Currently only #NMSettingEthtool supports it.
 *
 * Since: 1.26
 */
void
nm_setting_option_set (NMSetting *setting,
                       const char *opt_name,
                       GVariant *variant)
{
	GVariant *old_variant;
	gboolean changed_name;
	gboolean changed_value;
	GHashTable *hash;

	g_return_if_fail (NM_IS_SETTING (setting));
	g_return_if_fail (opt_name);

	hash = _nm_setting_option_hash (setting, variant != NULL);

	if (!variant) {
		if (hash) {
			if (g_hash_table_remove (hash, opt_name))
				_nm_setting_option_notify (setting, TRUE);
		}
		return;
	}

	/* Currently, it is a bug setting any option, unless the setting type supports it.
	 * And currently, only NMSettingEthtool supports it.
	 *
	 * In the future, more setting types may support it. Or we may relax this so
	 * that options can be attached to all setting types (to indicate "unsupported"
	 * settings for forward compatibility).
	 *
	 * As it is today, internal code will only add gendata options to NMSettingEthtool,
	 * and there exists not public API to add such options. Still, it is permissible
	 * to call get(), clear() and set(variant=NULL) also on settings that don't support
	 * it, as these operations don't add options.
	 */
	g_return_if_fail (_nm_setting_class_get_sett_info (NM_SETTING_GET_CLASS (setting))->detail.gendata_info);

	old_variant = g_hash_table_lookup (hash, opt_name);

	changed_name = (old_variant == NULL);
	changed_value =    changed_name
	                || !g_variant_equal (old_variant, variant);

	/* We always want to replace the variant, even if it has
	 * the same value according to g_variant_equal(). The reason
	 * is that we want to take a reference on @variant, because
	 * that is what the user might expect. */
	g_hash_table_insert (hash,
	                     g_strdup (opt_name),
	                     g_variant_ref_sink (variant));

	if (changed_value)
		_nm_setting_option_notify (setting, !changed_name);
}

/**
 * nm_setting_option_set_boolean:
 * @setting: the #NMSetting
 * @value: the value to set.
 *
 * Like nm_setting_option_set() to set a boolean GVariant.
 *
 * Since: 1.26
 */
void
nm_setting_option_set_boolean (NMSetting *setting,
                               const char *opt_name,
                               gboolean value)
{
	GVariant *old_variant;
	gboolean changed_name;
	gboolean changed_value;
	GHashTable *hash;

	g_return_if_fail (NM_IS_SETTING (setting));
	g_return_if_fail (opt_name);

	value = (!!value);

	hash = _nm_setting_option_hash (setting, TRUE);

	old_variant = g_hash_table_lookup (hash, opt_name);

	changed_name = (old_variant == NULL);
	changed_value =    changed_name
	                || (   !g_variant_is_of_type (old_variant, G_VARIANT_TYPE_BOOLEAN)
	                    || g_variant_get_boolean (old_variant) != value);

	g_hash_table_insert (hash,
	                     g_strdup (opt_name),
	                     g_variant_ref_sink (g_variant_new_boolean (value)));

	if (changed_value)
		_nm_setting_option_notify (setting, !changed_name);
}

/**
 * nm_setting_option_set_uint32:
 * @setting: the #NMSetting
 * @value: the value to set.
 *
 * Like nm_setting_option_set() to set a uint32 GVariant.
 *
 * Since: 1.26
 */
void
nm_setting_option_set_uint32 (NMSetting *setting,
                              const char *opt_name,
                              guint32 value)
{
	GVariant *old_variant;
	gboolean changed_name;
	gboolean changed_value;
	GHashTable *hash;

	g_return_if_fail (NM_IS_SETTING (setting));
	g_return_if_fail (opt_name);

	hash = _nm_setting_option_hash (setting, TRUE);

	old_variant = g_hash_table_lookup (hash, opt_name);

	changed_name = (old_variant == NULL);
	changed_value =    changed_name
	                || (   !g_variant_is_of_type (old_variant, G_VARIANT_TYPE_UINT32)
	                    || g_variant_get_uint32 (old_variant) != value);

	g_hash_table_insert (hash,
	                     g_strdup (opt_name),
	                     g_variant_ref_sink (g_variant_new_uint32 (value)));

	if (changed_value)
		_nm_setting_option_notify (setting, !changed_name);
}

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

static void
get_property (GObject *object, guint prop_id,
              GValue *value, GParamSpec *pspec)
{
	NMSetting *setting = NM_SETTING (object);

	switch (prop_id) {
	case PROP_NAME:
		g_value_set_string (value, nm_setting_get_name (setting));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

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

static void
nm_setting_init (NMSetting *setting)
{
}

static void
finalize (GObject *object)
{
	NMSettingPrivate *priv = NM_SETTING_GET_PRIVATE (object);

	if (priv->gendata) {
		g_free (priv->gendata->names);
		g_free (priv->gendata->values);
		g_hash_table_unref (priv->gendata->hash);
		g_slice_free (GenData, priv->gendata);
	}

	G_OBJECT_CLASS (nm_setting_parent_class)->finalize (object);
}

static void
nm_setting_class_init (NMSettingClass *setting_class)
{
	GObjectClass *object_class = G_OBJECT_CLASS (setting_class);

	g_type_class_add_private (setting_class, sizeof (NMSettingPrivate));

	object_class->get_property = get_property;
	object_class->finalize     = finalize;

	setting_class->update_one_secret         = update_one_secret;
	setting_class->get_secret_flags          = get_secret_flags;
	setting_class->set_secret_flags          = set_secret_flags;
	setting_class->compare_property          = compare_property;
	setting_class->clear_secrets             = clear_secrets;
	setting_class->for_each_secret           = for_each_secret;
	setting_class->duplicate_copy_properties = duplicate_copy_properties;
	setting_class->enumerate_values          = enumerate_values;
	setting_class->aggregate                 = aggregate;
	setting_class->init_from_dbus            = init_from_dbus;

	/**
	 * NMSetting:name:
	 *
	 * The setting's name, which uniquely identifies the setting within the
	 * connection.  Each setting type has a name unique to that type, for
	 * example "ppp" or "802-11-wireless" or "802-3-ethernet".
	 **/
	obj_properties[PROP_NAME] =
	    g_param_spec_string (NM_SETTING_NAME, "", "",
	                         NULL,
	                         G_PARAM_READABLE |
	                         G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
}
