Blob Blame History Raw
// SPDX-License-Identifier: LGPL-2.1+
/*
 * Copyright (C) 2019 Red Hat, Inc.
 */

#define NM_VALUE_TYPE_DEFINE_FUNCTIONS

#include "nm-default.h"

#include "nm-team-utils.h"

#include "nm-errors.h"
#include "nm-utils-private.h"
#include "nm-json.h"
#include "nm-glib-aux/nm-json-aux.h"
#include "nm-core-internal.h"
#include "nm-setting-team.h"
#include "nm-setting-team-port.h"

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

typedef enum {
	SET_FIELD_MODE_UNSET              = 0,
	SET_FIELD_MODE_SET                = 1,

	/* Sets the field as set, unless the field is at the default.
	 * This is the case for API that is called from NMSettingTeam/NMSettingTeamPort.
	 * This means, using libnm API to reset the value of a NMSetting to the default,
	 * will mark the field as unset.
	 * This is different from initializing the field when parsing JSON/GVariant. In
	 * that case an explicitly set field (even set to the default value) will be remembered
	 * to be set. */
	SET_FIELD_MODE_SET_UNLESS_DEFAULT = 2,
} SetFieldModeEnum;

typedef enum {
	RESET_JSON_NO  = FALSE,
	RESET_JSON_YES = TRUE,
} ResetJsonEnum;

/* we rely on "config" being the first. At various places we iterate over attribute types,
 * starting after "config".*/
G_STATIC_ASSERT (_NM_TEAM_ATTRIBUTE_0     == 0);
G_STATIC_ASSERT (NM_TEAM_ATTRIBUTE_CONFIG == 1);

static const char *const _valid_names_runner[] = {
	NM_SETTING_TEAM_RUNNER_BROADCAST,
	NM_SETTING_TEAM_RUNNER_ROUNDROBIN,
	NM_SETTING_TEAM_RUNNER_RANDOM,
	NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP,
	NM_SETTING_TEAM_RUNNER_LOADBALANCE,
	NM_SETTING_TEAM_RUNNER_LACP,
	NULL,
};

static const char *const _valid_names_runner_hwaddr_policy[] = {
	NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_SAME_ALL,
	NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_BY_ACTIVE,
	NM_SETTING_TEAM_RUNNER_HWADDR_POLICY_ONLY_ACTIVE,
	NULL,
};

static const char *const _valid_names_runner_tx_balancer[] = {
	"basic",
	NULL,
};

static const char *const _valid_names_runner_tx_hash[] = {
	"eth",
	"vlan",
	"ipv4",
	"ipv6",
	"ip",
	"l3",
	"l4",
	"tcp",
	"udp",
	"sctp",
	NULL,
};

static const char *const _valid_names_runner_agg_select_policy[] = {
	"lacp_prio",
	"lacp_prio_stable",
	"bandwidth",
	"count",
	"port_config",
	NULL,
};

typedef struct {
	NMTeamAttribute team_attr;
	const char *const*valid_runners;
} RunnerCompatElem;

static const RunnerCompatElem _runner_compat_lst[] = {
	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY,        NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_ACTIVEBACKUP), },
	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH,              NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LOADBALANCE,
	                                                                      NM_SETTING_TEAM_RUNNER_LACP), },
	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER,          NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LOADBALANCE,
	                                                                      NM_SETTING_TEAM_RUNNER_LACP), },
	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LOADBALANCE,
	                                                                      NM_SETTING_TEAM_RUNNER_LACP), },
	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE,               NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LACP), },
	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE,            NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LACP), },
	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO,             NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LACP), },
	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS,            NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LACP), },
	{ NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY,    NM_MAKE_STRV (NM_SETTING_TEAM_RUNNER_LACP), },
};

typedef struct {
	const char *const*js_keys;
	const char *property_name;
	NMValueTypUnion default_val;
	union {
		struct {
			gint32 min;
			gint32 max;
		} r_int32;
		struct {
			const char *const*valid_names;
		} r_string;
	} range;
	NMTeamAttribute team_attr;
	NMValueType value_type;
	guint8 field_offset;
	guint8 js_keys_len;
	bool for_master:1;
	bool for_port:1;
	bool has_range:1;
} TeamAttrData;

#define TEAM_ATTR_IDX(_is_port, _team_attr) \
	((  (!(_is_port) || (_team_attr) < _NM_TEAM_ATTRIBUTE_START) \
	  ? (int) (_team_attr) \
	  : (((int) (_NM_TEAM_ATTRIBUTE_MASTER_NUM - _NM_TEAM_ATTRIBUTE_START)) + ((int) (_team_attr)))) - 1)

#define TEAM_ATTR_IDX_CONFIG (TEAM_ATTR_IDX (FALSE, NM_TEAM_ATTRIBUTE_CONFIG))

static const TeamAttrData team_attr_datas[] = {

#define _JS_KEYS(...) \
		.js_keys = NM_MAKE_STRV (__VA_ARGS__), \
		.js_keys_len = NM_NARG (__VA_ARGS__)

#define _VAL_BOOL(_default) \
		.default_val.v_bool = (_default)

#define _VAL_INT32(_default) \
		.default_val.v_int32 = (_default)

#define _VAL_INT32_RANGE(_default, _min,_max) \
		_VAL_INT32 (_default), \
		.has_range = TRUE, \
		.range.r_int32 = { .min = _min, .max = _max, }

#define _VAL_STRING() \
		.default_val.v_string = NULL

#define _VAL_STRING_RANGE(_valid_names) \
		_VAL_STRING (), \
		.has_range = TRUE, \
		.range.r_string = { .valid_names = (_valid_names), }

#define _VAL_UNSPEC() \
		.default_val.v_string = (NULL)

#define _INIT(_is_port, _team_attr, field, _value_type, _property_name, ...) \
	[TEAM_ATTR_IDX (_is_port, _team_attr)] = { \
		.for_master    = (_team_attr) < _NM_TEAM_ATTRIBUTE_START || !(_is_port), \
		.for_port      = (_team_attr) < _NM_TEAM_ATTRIBUTE_START ||  (_is_port), \
		.team_attr     = (_team_attr), \
		.field_offset  = G_STRUCT_OFFSET (NMTeamSetting, _data_priv.field), \
		.value_type    = (_value_type), \
		.property_name = ""_property_name"", \
		__VA_ARGS__ \
	}

	_INIT (0, NM_TEAM_ATTRIBUTE_CONFIG,                             _js_str,                            NM_VALUE_TYPE_UNSPEC, NM_SETTING_TEAM_CONFIG,                                                                                                                                           ),

	_INIT (0, NM_TEAM_ATTRIBUTE_LINK_WATCHERS,                      link_watchers,                      NM_VALUE_TYPE_UNSPEC, NM_SETTING_TEAM_LINK_WATCHERS,               _JS_KEYS ("link_watch"),                                  _VAL_UNSPEC (),                                            ),

	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT,          master.notify_peers_count,          NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_NOTIFY_PEERS_COUNT,          _JS_KEYS ("notify_peers", "count"),                       _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL,       master.notify_peers_interval,       NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_NOTIFY_PEERS_INTERVAL,       _JS_KEYS ("notify_peers", "interval"),                    _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT,          master.mcast_rejoin_count,          NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_MCAST_REJOIN_COUNT,          _JS_KEYS ("mcast_rejoin", "count"),                       _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL,       master.mcast_rejoin_interval,       NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_MCAST_REJOIN_INTERVAL,       _JS_KEYS ("mcast_rejoin", "interval"),                    _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER,                      master.runner,                      NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER,                      _JS_KEYS ("runner", "name"),                              _VAL_STRING_RANGE (_valid_names_runner),                   ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY,        master.runner_hwaddr_policy,        NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER_HWADDR_POLICY,        _JS_KEYS ("runner", "hwaddr_policy"),                     _VAL_STRING_RANGE (_valid_names_runner_hwaddr_policy),     ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH,              master.runner_tx_hash,              NM_VALUE_TYPE_UNSPEC, NM_SETTING_TEAM_RUNNER_TX_HASH,              _JS_KEYS ("runner", "tx_hash"),                           _VAL_UNSPEC (),                                            ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER,          master.runner_tx_balancer,          NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER_TX_BALANCER,          _JS_KEYS ("runner", "tx_balancer", "name"),               _VAL_STRING_RANGE (_valid_names_runner_tx_balancer),       ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL, master.runner_tx_balancer_interval, NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_RUNNER_TX_BALANCER_INTERVAL, _JS_KEYS ("runner", "tx_balancer", "balancing_interval"), _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE,               master.runner_active,               NM_VALUE_TYPE_BOOL,   NM_SETTING_TEAM_RUNNER_ACTIVE,               _JS_KEYS ("runner", "active"),                            _VAL_BOOL (TRUE),                                          ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE,            master.runner_fast_rate,            NM_VALUE_TYPE_BOOL,   NM_SETTING_TEAM_RUNNER_FAST_RATE,            _JS_KEYS ("runner", "fast_rate"),                         _VAL_BOOL (FALSE),                                         ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO,             master.runner_sys_prio,             NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_RUNNER_SYS_PRIO,             _JS_KEYS ("runner", "sys_prio"),                          _VAL_INT32_RANGE (-1, 0, USHRT_MAX + 1),                   ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS,            master.runner_min_ports,            NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_RUNNER_MIN_PORTS,            _JS_KEYS ("runner", "min_ports"),                         _VAL_INT32_RANGE (-1, 1, UCHAR_MAX + 1),                   ),
	_INIT (0, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY,    master.runner_agg_select_policy,    NM_VALUE_TYPE_STRING, NM_SETTING_TEAM_RUNNER_AGG_SELECT_POLICY,    _JS_KEYS ("runner", "agg_select_policy"),                 _VAL_STRING_RANGE (_valid_names_runner_agg_select_policy), ),

	_INIT (1, NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID,                      port.queue_id,                      NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_PORT_QUEUE_ID,               _JS_KEYS ("queue_id"),                                    _VAL_INT32_RANGE (-1, 0, G_MAXINT32),                      ),
	_INIT (1, NM_TEAM_ATTRIBUTE_PORT_PRIO,                          port.prio,                          NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_PORT_PRIO,                   _JS_KEYS ("prio"),                                        _VAL_INT32 (0),                                            ),
	_INIT (1, NM_TEAM_ATTRIBUTE_PORT_STICKY,                        port.sticky,                        NM_VALUE_TYPE_BOOL,   NM_SETTING_TEAM_PORT_STICKY,                 _JS_KEYS ("sticky"),                                      _VAL_BOOL (FALSE),                                         ),
	_INIT (1, NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO,                     port.lacp_prio,                     NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_PORT_LACP_PRIO,              _JS_KEYS ("lacp_prio"),                                   _VAL_INT32_RANGE (-1, 0, USHRT_MAX + 1),                   ),
	_INIT (1, NM_TEAM_ATTRIBUTE_PORT_LACP_KEY,                      port.lacp_key,                      NM_VALUE_TYPE_INT32,  NM_SETTING_TEAM_PORT_LACP_KEY,               _JS_KEYS ("lacp_key"),                                    _VAL_INT32_RANGE (-1, 0, USHRT_MAX + 1),                   ),

#undef _INIT
};

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

typedef enum {
	LINK_WATCHER_ATTRIBUTE_NAME,
	LINK_WATCHER_ATTRIBUTE_DELAY_UP,
	LINK_WATCHER_ATTRIBUTE_DELAY_DOWN,
	LINK_WATCHER_ATTRIBUTE_INTERVAL,
	LINK_WATCHER_ATTRIBUTE_INIT_WAIT,
	LINK_WATCHER_ATTRIBUTE_MISSED_MAX,
	LINK_WATCHER_ATTRIBUTE_SOURCE_HOST,
	LINK_WATCHER_ATTRIBUTE_TARGET_HOST,
	LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE,
	LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE,
	LINK_WATCHER_ATTRIBUTE_VLANID,
	LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS,
} LinkWatcherAttribute;

#define _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL    LINK_WATCHER_ATTRIBUTE_NAME, \
                                                     LINK_WATCHER_ATTRIBUTE_DELAY_UP, \
                                                     LINK_WATCHER_ATTRIBUTE_DELAY_DOWN
#define _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING  LINK_WATCHER_ATTRIBUTE_NAME, \
                                                     LINK_WATCHER_ATTRIBUTE_INTERVAL, \
                                                     LINK_WATCHER_ATTRIBUTE_INIT_WAIT, \
                                                     LINK_WATCHER_ATTRIBUTE_MISSED_MAX, \
                                                     LINK_WATCHER_ATTRIBUTE_TARGET_HOST
#define _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING   LINK_WATCHER_ATTRIBUTE_NAME, \
                                                     LINK_WATCHER_ATTRIBUTE_INTERVAL, \
                                                     LINK_WATCHER_ATTRIBUTE_INIT_WAIT, \
                                                     LINK_WATCHER_ATTRIBUTE_MISSED_MAX, \
                                                     LINK_WATCHER_ATTRIBUTE_SOURCE_HOST, \
                                                     LINK_WATCHER_ATTRIBUTE_TARGET_HOST, \
                                                     LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE, \
                                                     LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, \
                                                     LINK_WATCHER_ATTRIBUTE_VLANID, \
                                                     LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS

typedef struct {
	const char *js_key;
	const char *dbus_name;
	NMValueTypUnion default_val;
	LinkWatcherAttribute link_watcher_attr;
	NMValueType value_type;
} LinkWatcherAttrData;

static const LinkWatcherAttrData link_watcher_attr_datas[] = {
#define _INIT(_link_watcher_attr, _js_key, _dbus_name, _value_type, ...) \
	[_link_watcher_attr] = { \
		.link_watcher_attr = (_link_watcher_attr), \
		.value_type = (_value_type), \
		.js_key = (""_js_key""), \
		.dbus_name = (""_dbus_name""), \
		__VA_ARGS__ \
	}
	_INIT (LINK_WATCHER_ATTRIBUTE_NAME,              "name",              "name",              NM_VALUE_TYPE_STRING,                          ),
	_INIT (LINK_WATCHER_ATTRIBUTE_DELAY_UP,          "delay_up",          "delay-up",          NM_VALUE_TYPE_INT,                             ),
	_INIT (LINK_WATCHER_ATTRIBUTE_DELAY_DOWN,        "delay_down",        "delay-down",        NM_VALUE_TYPE_INT,                             ),
	_INIT (LINK_WATCHER_ATTRIBUTE_INTERVAL,          "interval",          "interval",          NM_VALUE_TYPE_INT,                             ),
	_INIT (LINK_WATCHER_ATTRIBUTE_INIT_WAIT,         "init_wait",         "init-wait",         NM_VALUE_TYPE_INT,                             ),
	_INIT (LINK_WATCHER_ATTRIBUTE_MISSED_MAX,        "missed_max",        "missed-max",        NM_VALUE_TYPE_INT,    .default_val.v_int =  3, ),
	_INIT (LINK_WATCHER_ATTRIBUTE_SOURCE_HOST,       "source_host",       "source-host",       NM_VALUE_TYPE_STRING,                          ),
	_INIT (LINK_WATCHER_ATTRIBUTE_TARGET_HOST,       "target_host",       "target-host",       NM_VALUE_TYPE_STRING,                          ),
	_INIT (LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE,   "validate_active",   "validate-active",   NM_VALUE_TYPE_BOOL,                            ),
	_INIT (LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, "validate_inactive", "validate-inactive", NM_VALUE_TYPE_BOOL,                            ),
	_INIT (LINK_WATCHER_ATTRIBUTE_VLANID,            "vlanid",            "vlanid",            NM_VALUE_TYPE_INT,    .default_val.v_int = -1, ),
	_INIT (LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS,       "send_always",       "send-always",       NM_VALUE_TYPE_BOOL,                            ),
#undef _INIT
};

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

static const TeamAttrData *_team_attr_data_get (gboolean is_port,
                                                NMTeamAttribute team_attr);
static gpointer _team_setting_get_field (const NMTeamSetting *self,
                                         const TeamAttrData *attr_data);
static void _link_watcher_to_json (const NMTeamLinkWatcher *link_watcher,
                                   GString *gstr);

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

static void
_team_attr_data_ASSERT (const TeamAttrData *attr_data)
{
#if NM_MORE_ASSERTS > 5
	nm_assert (attr_data);
	if (attr_data->for_port)
		nm_assert (attr_data == _team_attr_data_get (TRUE, attr_data->team_attr));
	if (attr_data->for_master)
		nm_assert (attr_data == _team_attr_data_get (FALSE, attr_data->team_attr));
	nm_assert ((attr_data - team_attr_datas) == TEAM_ATTR_IDX (attr_data->for_port, attr_data->team_attr));
	nm_assert (attr_data->value_type > 0);
	nm_assert (attr_data->field_offset < sizeof (NMTeamSetting));
	nm_assert (attr_data->js_keys_len == NM_PTRARRAY_LEN (attr_data->js_keys));
	nm_assert (attr_data->property_name);
	{
		static int checked = 0;

		if (checked == 0) {
			checked = 1;

			for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++)
				_team_attr_data_ASSERT (attr_data);
		}
	}
#endif
}

static gboolean
_team_attr_data_is_relevant (const TeamAttrData *attr_data,
                             gboolean is_port)
{
	return   is_port
	       ? attr_data->for_port
	       : attr_data->for_master;
}

static const TeamAttrData *
_team_attr_data_get (gboolean is_port,
                     NMTeamAttribute team_attr)
{
	const int idx = TEAM_ATTR_IDX (is_port, team_attr);

	nm_assert (   idx >= 0
	           && idx < G_N_ELEMENTS (team_attr_datas));
	nm_assert (team_attr_datas[idx].team_attr == team_attr);
	nm_assert (_team_attr_data_is_relevant (&team_attr_datas[idx], is_port));

	return &team_attr_datas[idx];
}

static const TeamAttrData *
_team_attr_data_find_for_property_name (gboolean is_port,
                                        const char *property_name)
{
	const TeamAttrData *attr_data;

	for (attr_data = team_attr_datas; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
		if (   _team_attr_data_is_relevant (attr_data, is_port)
		    && nm_streq (property_name, attr_data->property_name))
			return attr_data;
	}
	return NULL;
}

static int
_team_attr_data_cmp (const TeamAttrData *attr_data,
                     gboolean is_port,
                     gconstpointer val_a,
                     gconstpointer val_b)
{
	const GPtrArray *v_ptrarray_a;
	const GPtrArray *v_ptrarray_b;
	guint len;

	_team_attr_data_ASSERT (attr_data);
	nm_assert (val_a);
	nm_assert (val_b);

	if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC)
		NM_CMP_RETURN (nm_value_type_cmp (attr_data->value_type, val_a, val_b));
	else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) {
		v_ptrarray_a = *((const GPtrArray *const*) val_a);
		v_ptrarray_b = *((const GPtrArray *const*) val_b);
		len = v_ptrarray_a ? v_ptrarray_a->len : 0u;
		NM_CMP_DIRECT (len, (v_ptrarray_b ? v_ptrarray_b->len : 0u));
		if (len > 0) {
			NM_CMP_RETURN (nm_team_link_watchers_cmp ((const NMTeamLinkWatcher *const*) v_ptrarray_a->pdata,
		                                              (const NMTeamLinkWatcher *const*) v_ptrarray_b->pdata,
		                                              len,
		                                              FALSE));
		}
	} else if (   !is_port
	           && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
		v_ptrarray_a = *((const GPtrArray *const*) val_a);
		v_ptrarray_b = *((const GPtrArray *const*) val_b);
		NM_CMP_RETURN (_nm_utils_strv_cmp_n (v_ptrarray_a ? (const char *const*) v_ptrarray_a->pdata : NULL,
		                                     v_ptrarray_a ? v_ptrarray_a->len : 0u,
		                                     v_ptrarray_b ? (const char *const*) v_ptrarray_b->pdata : NULL,
		                                     v_ptrarray_b ? v_ptrarray_b->len : 0u));
	} else
		nm_assert_not_reached ();
	return 0;
}

static gboolean
_team_attr_data_equal (const TeamAttrData *attr_data,
                       gboolean is_port,
                       gconstpointer val_a,
                       gconstpointer val_b)
{
	return _team_attr_data_cmp (attr_data, is_port, val_a, val_b) == 0;
}

static void
_team_attr_data_copy (const TeamAttrData *attr_data,
                      gboolean is_port,
                      gpointer dst,
                      gconstpointer src)
{
	GPtrArray *v_ptrarray_dst;
	const GPtrArray *v_ptrarray_src;
	GPtrArray *dst_array;
	guint i, len;

	if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC)
		nm_value_type_copy (attr_data->value_type, dst, src);
	else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) {
		v_ptrarray_src = *((const GPtrArray *const *) src);
		v_ptrarray_dst = *((GPtrArray **) dst);
		len = (v_ptrarray_src ? v_ptrarray_src->len : 0u);

		if (len == 0) {
			if (v_ptrarray_dst)
				g_ptr_array_set_size (v_ptrarray_dst, 0);
		} else {
			dst_array = g_ptr_array_new_full (len, (GDestroyNotify) nm_team_link_watcher_unref);
			for (i = 0; i < len; i++) {
				if (v_ptrarray_src->pdata[i]) {
					nm_team_link_watcher_ref (v_ptrarray_src->pdata[i]);
					g_ptr_array_add (dst_array,v_ptrarray_src->pdata[i]);
				}
			}
			if (v_ptrarray_dst)
				g_ptr_array_unref (v_ptrarray_dst);
			*((GPtrArray **) dst) = dst_array;
		}
	} else if (   !is_port
	           && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
		v_ptrarray_src = *((const GPtrArray *const *) src);
		v_ptrarray_dst = *((GPtrArray **) dst);
		len = (v_ptrarray_src ? v_ptrarray_src->len : 0u);

		if (   v_ptrarray_src
		    && v_ptrarray_src->len > 0) {
			dst_array = g_ptr_array_new_full (v_ptrarray_src->len, g_free);
			for (i = 0; i < v_ptrarray_src->len; i++)
				g_ptr_array_add (dst_array, g_strdup (v_ptrarray_src->pdata[i]));
		} else
			dst_array = NULL;
		if (v_ptrarray_dst)
			g_ptr_array_unref (v_ptrarray_dst);
		*((GPtrArray **) dst) = dst_array;
	} else
		nm_assert_not_reached ();
}

static void
_team_attr_data_to_json (const TeamAttrData *attr_data,
                         gboolean is_port,
                         GString *gstr,
                         gconstpointer p_field)
{
	guint i;

	_team_attr_data_ASSERT (attr_data);
	nm_assert (p_field);

	nm_json_aux_gstr_append_obj_name (gstr,
	                                  attr_data->js_keys[attr_data->js_keys_len - 1],
	                                  '\0');

	if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) {
		nm_value_type_to_json (attr_data->value_type, gstr, p_field);
		return;
	}

	if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) {
		const GPtrArray *v_ptrarray = *((const GPtrArray *const*) p_field);

		if (!v_ptrarray)
			g_string_append (gstr, "null");
		else if (v_ptrarray->len == 0)
			g_string_append (gstr, "[ ]");
		else if (v_ptrarray->len == 1)
			_link_watcher_to_json (v_ptrarray->pdata[0], gstr);
		else {
			g_string_append (gstr, "[ ");
			for (i = 0; i < v_ptrarray->len; i++) {
				if (i > 0)
					nm_json_aux_gstr_append_delimiter (gstr);
				_link_watcher_to_json (v_ptrarray->pdata[i], gstr);
			}
			g_string_append (gstr, " ]");
		}
		return;
	}

	if (   !is_port
	    && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
		const GPtrArray *v_ptrarray = *((const GPtrArray *const*) p_field);

		if (!v_ptrarray)
			g_string_append (gstr, "null");
		else {
			g_string_append (gstr, "[ ");
			for (i = 0; i < v_ptrarray->len; i++) {
				if (i > 0)
					nm_json_aux_gstr_append_delimiter (gstr);
				nm_json_aux_gstr_append_string (gstr, v_ptrarray->pdata[i]);
			}
			g_string_append (gstr, i > 0 ? " ]" : "]");
		}
		return;
	}

	nm_assert_not_reached ();
}

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

static void
_team_setting_ASSERT (const NMTeamSetting *self)
{
	nm_assert (self);
	nm_assert (!self->d._js_str_need_synthetize || !self->d._js_str);
#if NM_MORE_ASSERTS > 2
	if (!self->d.strict_validated) {
		nm_assert (!self->d._js_str_need_synthetize);
		nm_assert (self->d._js_str);
	}
	nm_assert (self->d.link_watchers);
	nm_assert (   self->d.is_port
	           || !self->d.master.runner_tx_hash
	           || self->d.master.runner_tx_hash->len > 0);
#endif
}

static gboolean
_team_setting_has_field (const NMTeamSetting *self,
                         const TeamAttrData *attr_data)
{
	_team_setting_ASSERT (self);
	return NM_FLAGS_ALL (self->d.has_fields_mask, nm_team_attribute_to_flags (attr_data->team_attr));
}

static gboolean
_team_setting_has_fields_any_v (const NMTeamSetting *self,
                                const NMTeamAttribute *team_attrs,
                                gsize n_team_attrs)
{
	gsize i;

	for (i = 0; i < n_team_attrs; i++) {
		const TeamAttrData *attr_data = _team_attr_data_get (self->d.is_port, team_attrs[i]);

		if (_team_setting_has_field (self, attr_data))
			return TRUE;
	}
	return FALSE;
}

#define _team_setting_has_fields_any(self, ...) \
   _team_setting_has_fields_any_v ((self), ((const NMTeamAttribute []) { __VA_ARGS__ }), NM_NARG (__VA_ARGS__))

static void
_team_setting_has_field_set (NMTeamSetting *self,
                             const TeamAttrData *attr_data,
                             SetFieldModeEnum set_field_mode)
{
	guint32 mask = nm_team_attribute_to_flags (attr_data->team_attr);

	_team_setting_ASSERT (self);

	switch (set_field_mode) {
	case SET_FIELD_MODE_UNSET:
		goto do_unset;
	case SET_FIELD_MODE_SET:
		goto do_set;
	case SET_FIELD_MODE_SET_UNLESS_DEFAULT:
		if (_team_attr_data_equal (attr_data,
		                           self->d.is_port,
		                           _team_setting_get_field (self, attr_data),
		                           &attr_data->default_val))
			goto do_unset;
		goto do_set;
	}
	nm_assert_not_reached ();

do_unset:
	self->_data_priv.has_fields_mask &= ~mask;
	return;
do_set:
	self->_data_priv.has_fields_mask |= mask;
}

static gpointer
_team_setting_get_field (const NMTeamSetting *self,
                         const TeamAttrData *attr_data)
{
	_team_setting_ASSERT (self);
	_team_attr_data_ASSERT (attr_data);
	nm_assert (_team_attr_data_is_relevant (attr_data, self->d.is_port));

#if NM_MORE_ASSERTS > 5
	if (   attr_data->for_master
	    && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO )
		nm_assert ((gpointer) (((char *) self) + attr_data->field_offset) == &self->d.master.runner_sys_prio);
#endif

	return (((char *) self) + attr_data->field_offset);
}

static guint32
_team_setting_attribute_changed (NMTeamSetting *self,
                                 const TeamAttrData *attr_data,
                                 gboolean changed,
                                 SetFieldModeEnum set_field_mode,
                                 ResetJsonEnum reset_json)
{
	guint32 changed_flags;

	_team_setting_has_field_set (self, attr_data, set_field_mode);

	if (!reset_json) {
		return   changed
		       ? nm_team_attribute_to_flags (attr_data->team_attr)
		       : 0u;
	}

	if (!changed) {
		/* a regular attribute was set, but the value did not change.
		 *
		 * If we previously were in non-strict mode, then
		 *
		 * - switch to strict-mode. Clearly the user set a regular attribute
		 *   and hence now we want to validate the setting.
		 *
		 * - clear the JSON string. We need to regenerate it.
		 */
		if (self->_data_priv.strict_validated)
			return 0;
		changed_flags = nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);
	} else {
		changed_flags =   nm_team_attribute_to_flags (attr_data->team_attr)
		                | nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);
	}

	nm_clear_g_free ((char **) &self->_data_priv._js_str);
	self->_data_priv.strict_validated = TRUE;
	self->_data_priv._js_str_need_synthetize = TRUE;

	return changed_flags;
}

static guint32
_team_setting_attribute_changed_attr (NMTeamSetting *self,
                                      NMTeamAttribute team_attr,
                                      gboolean changed,
                                      SetFieldModeEnum set_field_mode,
                                      ResetJsonEnum reset_json)
{
	return _team_setting_attribute_changed (self,
	                                        _team_attr_data_get (self->d.is_port, team_attr),
	                                        changed,
	                                        set_field_mode,
	                                        reset_json);
}

static gboolean
_team_setting_field_to_json (const NMTeamSetting *self,
                             GString *gstr,
                             gboolean prepend_delimiter,
                             const TeamAttrData *attr_data)
{
	if (!_team_setting_has_field (self, attr_data))
		return FALSE;

	if (prepend_delimiter)
		nm_json_aux_gstr_append_delimiter (gstr);
	_team_attr_data_to_json (attr_data,
	                         self->d.is_port,
	                         gstr,
	                         _team_setting_get_field (self, attr_data));
	return TRUE;
}

static gboolean
_team_setting_fields_to_json_maybe (const NMTeamSetting *self,
                                    GString *gstr,
                                    gboolean prepend_delimiter,
                                    const NMTeamAttribute *team_attrs_lst,
                                    gsize team_attrs_lst_len)
{
	gsize i;
	gboolean any_added = FALSE;

	for (i = 0; i < team_attrs_lst_len; i++) {
		if (_team_setting_field_to_json (self,
		                                 gstr,
		                                 prepend_delimiter,
		                                 _team_attr_data_get (self->d.is_port, team_attrs_lst[i]))) {
			any_added = TRUE;
			prepend_delimiter = TRUE;
		}
	}
	return any_added;
}

static guint32
_team_setting_set (NMTeamSetting *self,
                   gboolean modify,
                   const bool *has_lst,
                   const NMValueTypUnion *val_lst)
{
	guint32 changed_flags = 0;
	const TeamAttrData *attr_data;

	nm_assert ((!has_lst) == (!val_lst));

	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
		const NMValueTypUnion *p_val;
		gconstpointer p_field;
		gboolean has_field;

		if (!_team_attr_data_is_relevant (attr_data, self->d.is_port))
			continue;

		has_field = (has_lst && has_lst[attr_data->team_attr]);

		p_val = has_field
		        ? &val_lst[attr_data->team_attr]
		        : &attr_data->default_val;

		p_field = _team_setting_get_field (self, attr_data);

		if (!_team_attr_data_equal (attr_data,
		                            self->d.is_port,
		                            p_val,
		                            p_field)) {
			if (modify) {
				_team_attr_data_copy (attr_data,
				                      self->d.is_port,
				                      (gpointer) p_field,
				                      p_val);
			}
			changed_flags |= nm_team_attribute_to_flags (attr_data->team_attr);
		}

		if (modify) {
			_team_setting_has_field_set (self,
			                             attr_data,
			                               has_field
			                             ? SET_FIELD_MODE_SET
			                             : SET_FIELD_MODE_UNSET);
		}
	}

	return changed_flags;
}

static guint32
_team_setting_check_default (const NMTeamSetting *self)
{
	return _team_setting_set ((NMTeamSetting *) self, FALSE, NULL, NULL);
}

static guint32
_team_setting_set_default (NMTeamSetting *self)
{
	return _team_setting_set (self, TRUE, NULL, NULL);
}

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

gconstpointer
_nm_team_setting_value_get (const NMTeamSetting *self,
                            NMTeamAttribute team_attr,
                            NMValueType value_type)
{
	const TeamAttrData *attr_data = _team_attr_data_get (self->d.is_port, team_attr);

	nm_assert (value_type == attr_data->value_type);

	nm_assert (   _team_setting_has_field (self, attr_data)
	           || _team_attr_data_equal (attr_data,
	                                     self->d.is_port,
	                                     _team_setting_get_field (self, attr_data),
	                                     &attr_data->default_val));
	return _team_setting_get_field (self, attr_data);
}

static guint32
_team_setting_value_set (NMTeamSetting *self,
                         const TeamAttrData *attr_data,
                         gconstpointer val,
                         SetFieldModeEnum set_field_mode,
                         ResetJsonEnum reset_json)
{
	gpointer p_field;
	gboolean changed;

	nm_assert (self);
	_team_attr_data_ASSERT (attr_data);
	nm_assert (val);

	p_field = _team_setting_get_field (self, attr_data);

	changed = !_team_attr_data_equal (attr_data, self->d.is_port, p_field, val);
	if (changed)
		nm_value_type_copy (attr_data->value_type, p_field, val);
	return _team_setting_attribute_changed (self, attr_data, changed, set_field_mode, reset_json);
}

guint32
nm_team_setting_value_reset (NMTeamSetting *self,
                             NMTeamAttribute team_attr,
                             gboolean to_default /* or else unset */)
{
	const TeamAttrData *attr_data;

	nm_assert (self);

	attr_data = _team_attr_data_get (self->d.is_port, team_attr);

	return _team_setting_value_set (self,
	                                attr_data,
	                                &attr_data->default_val,
	                                  to_default
	                                ? SET_FIELD_MODE_SET
	                                : SET_FIELD_MODE_UNSET,
	                                RESET_JSON_YES);
}

guint32
_nm_team_setting_value_set (NMTeamSetting *self,
                            NMTeamAttribute team_attr,
                            NMValueType value_type,
                            gconstpointer val)
{
	const TeamAttrData *attr_data;

	nm_assert (self);

	attr_data = _team_attr_data_get (self->d.is_port, team_attr);

	nm_assert (value_type == attr_data->value_type);

	return _team_setting_value_set (self,
	                                attr_data,
	                                val,
	                                SET_FIELD_MODE_SET_UNLESS_DEFAULT,
	                                RESET_JSON_YES);
}

guint32
nm_team_setting_value_link_watchers_add (NMTeamSetting *self,
                                         const NMTeamLinkWatcher *link_watcher)
{
	guint i;
	gboolean changed;

	for (i = 0; i < self->d.link_watchers->len; i++) {
		if (nm_team_link_watcher_equal (self->d.link_watchers->pdata[i], link_watcher)) {
			changed = FALSE;
			goto out;
		}
	}
	changed = TRUE;
	g_ptr_array_add ((GPtrArray *) self->d.link_watchers,
	                 _nm_team_link_watcher_ref ((NMTeamLinkWatcher *) link_watcher));
out:
	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, changed, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES);
}

guint32
nm_team_setting_value_link_watchers_remove_by_value (NMTeamSetting *self,
                                                     const NMTeamLinkWatcher *link_watcher)
{
	guint i;

	for (i = 0; i < self->d.link_watchers->len; i++) {
		if (nm_team_link_watcher_equal (self->d.link_watchers->pdata[i],
		                                link_watcher))
			return nm_team_setting_value_link_watchers_remove (self, i);
	}
	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, FALSE, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES);
}

guint32
nm_team_setting_value_link_watchers_remove (NMTeamSetting *self,
                                            guint idx)
{
	g_ptr_array_remove_index ((GPtrArray *) self->d.link_watchers, idx);
	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, TRUE, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES);
}

static guint32
_team_setting_value_link_watchers_set_list (NMTeamSetting *self,
                                            const NMTeamLinkWatcher *const*arr,
                                            guint len,
                                            SetFieldModeEnum set_field_mode,
                                            ResetJsonEnum reset_json)
{
	gboolean changed;

	if (   self->d.link_watchers->len == len
	    && nm_team_link_watchers_cmp ((const NMTeamLinkWatcher *const*) self->d.link_watchers->pdata,
	                                  arr,
	                                  len,
	                                  FALSE) == 0) {
		changed = FALSE;
		goto out;
	}

	changed = TRUE;
	if (len == 0)
		g_ptr_array_set_size ((GPtrArray *) self->d.link_watchers, 0);
	else {
		_nm_unused gs_unref_ptrarray GPtrArray *old_val_destroy = NULL;
		guint i;

		old_val_destroy = (GPtrArray *) g_steal_pointer (&self->_data_priv.link_watchers);

		self->_data_priv.link_watchers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);

		for (i = 0; i < len; i++) {
			if (arr[i]) {
				g_ptr_array_add ((GPtrArray *) self->d.link_watchers,
				                 _nm_team_link_watcher_ref ((NMTeamLinkWatcher *) arr[i]));
			}
		}
	}

out:
	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_LINK_WATCHERS, changed, set_field_mode, reset_json);
}

guint32
nm_team_setting_value_link_watchers_set_list (NMTeamSetting *self,
                                              const NMTeamLinkWatcher *const*arr,
                                              guint len)
{
	return _team_setting_value_link_watchers_set_list (self,
	                                                   arr,
	                                                   len,
	                                                   SET_FIELD_MODE_SET_UNLESS_DEFAULT,
	                                                   RESET_JSON_YES);
}

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

guint32
nm_team_setting_value_master_runner_tx_hash_add (NMTeamSetting *self,
                                                 const char *txhash)
{
	gboolean changed;
	guint i;

	if (!self->d.master.runner_tx_hash)
		self->_data_priv.master.runner_tx_hash = g_ptr_array_new_with_free_func (g_free);
	else {
		for (i = 0; i < self->d.master.runner_tx_hash->len; i++) {
			if (nm_streq (txhash, self->d.master.runner_tx_hash->pdata[i])) {
				changed = FALSE;
				goto out;
			}
		}
	}
	changed = TRUE;
	g_ptr_array_add ((GPtrArray *) self->d.master.runner_tx_hash, g_strdup (txhash));
out:
	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, changed, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES);
}

guint32
nm_team_setting_value_master_runner_tx_hash_remove (NMTeamSetting *self,
                                                    guint idx)
{
	g_ptr_array_remove_index ((GPtrArray *) self->d.master.runner_tx_hash, idx);
	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, TRUE, SET_FIELD_MODE_SET_UNLESS_DEFAULT, RESET_JSON_YES);
}

static guint32
_team_setting_value_master_runner_tx_hash_set_list (NMTeamSetting *self,
                                                    const char *const*arr,
                                                    guint len,
                                                    SetFieldModeEnum set_field_mode,
                                                    ResetJsonEnum reset_json)
{
	_nm_unused gs_unref_ptrarray GPtrArray *old_val_destroy = NULL;
	gboolean changed;
	guint i;

	if (_nm_utils_strv_cmp_n (self->d.master.runner_tx_hash ? (const char *const*) self->d.master.runner_tx_hash->pdata : NULL,
	                          self->d.master.runner_tx_hash ? self->d.master.runner_tx_hash->len : 0u,
	                          arr,
	                          len) == 0) {
		changed = FALSE;
		goto out;
	}

	changed = TRUE;

	old_val_destroy = (GPtrArray *) g_steal_pointer (&self->_data_priv.master.runner_tx_hash);

	for (i = 0; i < len; i++) {
		if (!arr[i])
			continue;
		if (!self->d.master.runner_tx_hash)
			self->_data_priv.master.runner_tx_hash = g_ptr_array_new_with_free_func (g_free);
		g_ptr_array_add ((GPtrArray *) self->d.master.runner_tx_hash, g_strdup (arr[i]));
	}

out:
	return _team_setting_attribute_changed_attr (self, NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH, changed, set_field_mode, reset_json);
}

guint32
nm_team_setting_value_master_runner_tx_hash_set_list (NMTeamSetting *self,
                                                      const char *const*arr,
                                                      guint len)
{
	return _team_setting_value_master_runner_tx_hash_set_list (self,
	                                                           arr,
	                                                           len,
	                                                           SET_FIELD_MODE_SET_UNLESS_DEFAULT,
	                                                           RESET_JSON_YES);
}

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

#define _LINK_WATCHER_ATTR_GET(args, link_watcher_attribute, _value_type) \
	({ \
		const NMValueTypUnioMaybe *const _args = (args); \
		\
		nm_assert (link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \
		\
		  _args[(link_watcher_attribute)].has \
		? &_args[(link_watcher_attribute)].val \
		: &link_watcher_attr_datas[(link_watcher_attribute)].default_val; \
	})
#define _LINK_WATCHER_ATTR_GET_BOOL(args, link_watcher_attribute)   (_LINK_WATCHER_ATTR_GET (args, link_watcher_attribute, NM_VALUE_TYPE_BOOL   )->v_bool)
#define _LINK_WATCHER_ATTR_GET_INT(args, link_watcher_attribute)    (_LINK_WATCHER_ATTR_GET (args, link_watcher_attribute, NM_VALUE_TYPE_INT    )->v_int)
#define _LINK_WATCHER_ATTR_GET_STRING(args, link_watcher_attribute) (_LINK_WATCHER_ATTR_GET (args, link_watcher_attribute, NM_VALUE_TYPE_STRING )->v_string)

#define _LINK_WATCHER_ATTR_SET(args, link_watcher_attribute, _value_type, c_type, val) \
	({ \
		nm_assert (link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \
		\
		NM_VALUE_TYP_UNIO_MAYBE_SET (&(args)[(link_watcher_attribute)], c_type, (val)); \
	})
#define _LINK_WATCHER_ATTR_SET_BOOL(args, link_watcher_attribute, val)   _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_BOOL,   v_bool,   (val))
#define _LINK_WATCHER_ATTR_SET_INT(args, link_watcher_attribute, val)    _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_INT,    v_int,    (val))
#define _LINK_WATCHER_ATTR_SET_STRING(args, link_watcher_attribute, val) _LINK_WATCHER_ATTR_SET((args), (link_watcher_attribute), NM_VALUE_TYPE_STRING, v_string, (val))

static void
_link_watcher_unpack (const NMTeamLinkWatcher *link_watcher,
                      NMValueTypUnioMaybe args[static G_N_ELEMENTS (link_watcher_attr_datas)])
{
	const char *v_name = nm_team_link_watcher_get_name (link_watcher);
	NMTeamLinkWatcherArpPingFlags v_arp_ping_flags;

	memset (args, 0, sizeof (args[0]) * G_N_ELEMENTS (link_watcher_attr_datas));

	_LINK_WATCHER_ATTR_SET_STRING (args, LINK_WATCHER_ATTRIBUTE_NAME, v_name);

	if (nm_streq (v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
		_LINK_WATCHER_ATTR_SET_INT (args, LINK_WATCHER_ATTRIBUTE_DELAY_UP,   nm_team_link_watcher_get_delay_up (link_watcher));
		_LINK_WATCHER_ATTR_SET_INT (args, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN, nm_team_link_watcher_get_delay_down (link_watcher));
	} else if (NM_IN_STRSET (v_name, NM_TEAM_LINK_WATCHER_NSNA_PING,
	                                 NM_TEAM_LINK_WATCHER_ARP_PING)) {
		_LINK_WATCHER_ATTR_SET_INT    (args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT,   nm_team_link_watcher_get_init_wait (link_watcher));
		_LINK_WATCHER_ATTR_SET_INT    (args, LINK_WATCHER_ATTRIBUTE_INTERVAL,    nm_team_link_watcher_get_interval (link_watcher));
		_LINK_WATCHER_ATTR_SET_INT    (args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX,  nm_team_link_watcher_get_missed_max (link_watcher));
		_LINK_WATCHER_ATTR_SET_STRING (args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST, nm_team_link_watcher_get_target_host (link_watcher));
		if (nm_streq (v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) {
			v_arp_ping_flags = nm_team_link_watcher_get_flags (link_watcher);
			_LINK_WATCHER_ATTR_SET_INT    (args, LINK_WATCHER_ATTRIBUTE_VLANID,            nm_team_link_watcher_get_vlanid (link_watcher));
			_LINK_WATCHER_ATTR_SET_STRING (args, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST,       nm_team_link_watcher_get_source_host (link_watcher));
			_LINK_WATCHER_ATTR_SET_BOOL   (args, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE,   NM_FLAGS_HAS (v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE));
			_LINK_WATCHER_ATTR_SET_BOOL   (args, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE, NM_FLAGS_HAS (v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE));
			_LINK_WATCHER_ATTR_SET_BOOL   (args, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS,       NM_FLAGS_HAS (v_arp_ping_flags, NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS));
		}
	}
}

static void
_link_watcher_to_json (const NMTeamLinkWatcher *link_watcher,
                       GString *gstr)
{
	NMValueTypUnioMaybe args[G_N_ELEMENTS (link_watcher_attr_datas)];
	int i;
	gboolean is_first = TRUE;

	if (!link_watcher) {
		g_string_append (gstr, "null");
		return;
	}

	_link_watcher_unpack (link_watcher, args);

	g_string_append (gstr, "{ ");

	for (i = 0; i < (int) G_N_ELEMENTS (link_watcher_attr_datas); i++) {
		const NMValueTypUnioMaybe *p_val = &args[i];
		const LinkWatcherAttrData *attr_data = &link_watcher_attr_datas[i];

		if (!p_val->has)
			continue;
		if (nm_value_type_equal (attr_data->value_type, &attr_data->default_val, &p_val->val))
			continue;

		if (is_first)
			is_first = FALSE;
		else
			nm_json_aux_gstr_append_delimiter (gstr);
		nm_json_aux_gstr_append_obj_name (gstr, attr_data->js_key, '\0');
		nm_value_type_to_json (attr_data->value_type, gstr, &p_val->val);
	}

	g_string_append (gstr, " }");
}

#if WITH_JSON_VALIDATION
static NMTeamLinkWatcher *
_link_watcher_from_json (const json_t *root_js_obj,
                         gboolean *out_unrecognized_content)
{
	NMValueTypUnioMaybe args[G_N_ELEMENTS (link_watcher_attr_datas)] = { };
	const char *j_key;
	json_t *j_val;
	const char *v_name;
	NMTeamLinkWatcher *result = NULL;

	if (!json_is_object (root_js_obj))
		goto fail;

	json_object_foreach ((json_t *) root_js_obj, j_key, j_val) {
		const LinkWatcherAttrData *attr_data = NULL;
		NMValueTypUnioMaybe *parse_result;

		if (j_key) {
			int i;

			for (i = 0; i < (int) G_N_ELEMENTS (link_watcher_attr_datas); i++) {
				if (nm_streq (link_watcher_attr_datas[i].js_key, j_key)) {
					attr_data = &link_watcher_attr_datas[i];
					break;
				}
			}
		}
		if (!attr_data) {
			*out_unrecognized_content = TRUE;
			continue;
		}

		parse_result = &args[attr_data->link_watcher_attr];

		if (parse_result->has)
			*out_unrecognized_content = TRUE;

		if (!nm_value_type_from_json (attr_data->value_type, j_val, &parse_result->val))
			*out_unrecognized_content = TRUE;
		else
			parse_result->has = TRUE;
	}

#define _PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES(_parse_results, ...) \
	({ \
		int _i; \
		\
		for (_i = 0; _i < (int) G_N_ELEMENTS ((_parse_results)); _i++) { \
			if (   (_parse_results)[_i].has \
			    && !NM_IN_SET ((LinkWatcherAttribute) _i, __VA_ARGS__)) \
				break; \
		} \
		\
		(_i == (int) G_N_ELEMENTS ((_parse_results))); \
	})

	v_name = _LINK_WATCHER_ATTR_GET_STRING (args, LINK_WATCHER_ATTRIBUTE_NAME);

	if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
		if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES (args, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL))
			*out_unrecognized_content = TRUE;
		result = nm_team_link_watcher_new_ethtool (_LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_DELAY_UP),
		                                           _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN),
		                                           NULL);
	} else if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_NSNA_PING)) {
		if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES (args, _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING))
			*out_unrecognized_content = TRUE;
		result = nm_team_link_watcher_new_nsna_ping (_LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT),
		                                             _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_INTERVAL),
		                                             _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX),
		                                             _LINK_WATCHER_ATTR_GET_STRING (args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST),
		                                             NULL);
	} else if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) {
		NMTeamLinkWatcherArpPingFlags v_flags = NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE;

		if (_PARSE_RESULT_HAS_UNEXPECTED_ATTRIBUTES (args, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING))
			*out_unrecognized_content = TRUE;

		if (_LINK_WATCHER_ATTR_GET_BOOL (args, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE))
			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE;
		if (_LINK_WATCHER_ATTR_GET_BOOL (args, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE))
			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE;
		if (_LINK_WATCHER_ATTR_GET_BOOL (args, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS))
			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS;

		result = nm_team_link_watcher_new_arp_ping2 (_LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_INIT_WAIT),
		                                             _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_INTERVAL),
		                                             _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_MISSED_MAX),
		                                             _LINK_WATCHER_ATTR_GET_INT (args, LINK_WATCHER_ATTRIBUTE_VLANID),
		                                             _LINK_WATCHER_ATTR_GET_STRING (args, LINK_WATCHER_ATTRIBUTE_TARGET_HOST),
		                                             _LINK_WATCHER_ATTR_GET_STRING (args, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST),
		                                             v_flags,
		                                             NULL);
	}

	if (result)
		return result;
fail:
	*out_unrecognized_content = TRUE;
	return NULL;
}
#endif

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

static GVariant *
_link_watcher_to_variant (const NMTeamLinkWatcher *link_watcher)
{
	NMValueTypUnioMaybe args[G_N_ELEMENTS (link_watcher_attr_datas)];
	GVariantBuilder builder;
	int i;

	if (!link_watcher)
		return NULL;

	_link_watcher_unpack (link_watcher, args);

	if (!args[LINK_WATCHER_ATTRIBUTE_NAME].has)
		return NULL;

	g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));

	for (i = 0; i < (int) G_N_ELEMENTS (link_watcher_attr_datas); i++) {
		const NMValueTypUnioMaybe *p_val = &args[i];
		const LinkWatcherAttrData *attr_data = &link_watcher_attr_datas[i];
		GVariant *v;

		if (!p_val->has)
			continue;
		if (nm_value_type_equal (attr_data->value_type, &attr_data->default_val, &p_val->val))
			continue;

		if (attr_data->value_type == NM_VALUE_TYPE_INT)
			v = g_variant_new_int32 (p_val->val.v_int);
		else {
			v = nm_value_type_to_variant (attr_data->value_type,
			                              &p_val->val);
		}
		if (!v)
			continue;

		nm_assert (g_variant_is_floating (v));
		g_variant_builder_add (&builder,
		                       "{sv}",
		                       attr_data->dbus_name,
		                       v);
	}

	return g_variant_builder_end (&builder);
}

#define _LINK_WATCHER_ATTR_VARGET(variants, link_watcher_attribute, _value_type, c_type, _cmd) \
	({ \
		GVariant *const*_variants = (variants); \
		GVariant *_cc; \
		\
		nm_assert (link_watcher_attr_datas[(link_watcher_attribute)].value_type == (_value_type)); \
		\
		  (_cc = _variants[(link_watcher_attribute)]) \
		? (_cmd) \
		: link_watcher_attr_datas[(link_watcher_attribute)].default_val.c_type; \
	})
#define _LINK_WATCHER_ATTR_VARGET_BOOL(variants, link_watcher_attribute)   (_LINK_WATCHER_ATTR_VARGET (variants, link_watcher_attribute, NM_VALUE_TYPE_BOOL,   v_bool,   g_variant_get_boolean (_cc)      ))
#define _LINK_WATCHER_ATTR_VARGET_INT(variants, link_watcher_attribute)    (_LINK_WATCHER_ATTR_VARGET (variants, link_watcher_attribute, NM_VALUE_TYPE_INT,    v_int,    g_variant_get_int32 (_cc)        ))
#define _LINK_WATCHER_ATTR_VARGET_STRING(variants, link_watcher_attribute) (_LINK_WATCHER_ATTR_VARGET (variants, link_watcher_attribute, NM_VALUE_TYPE_STRING, v_string, g_variant_get_string (_cc, NULL) ))

static void
_variants_list_link_watcher_unref_auto (GVariant *(*p_variants)[])
{
	int i;

	for (i = 0; i < (int) G_N_ELEMENTS (link_watcher_attr_datas); i++)
		nm_g_variant_unref ((*p_variants)[i]);
}

static NMTeamLinkWatcher *
_link_watcher_from_variant (GVariant *watcher_var,
                            gboolean strict_parsing,
                            GError **error)
{
	nm_auto (_variants_list_link_watcher_unref_auto) GVariant *variants[G_N_ELEMENTS (link_watcher_attr_datas)] = { NULL, };
	const char *v_key;
	GVariant *v_val;
	const char *v_name;
	GVariantIter iter;

	g_return_val_if_fail (g_variant_is_of_type (watcher_var, G_VARIANT_TYPE ("a{sv}")), NULL);

	g_variant_iter_init (&iter, watcher_var);
	while (g_variant_iter_next (&iter, "{&sv}", &v_key, &v_val)) {
		_nm_unused gs_unref_variant GVariant *v_val_free = v_val;
		const LinkWatcherAttrData *attr_data = NULL;
		const GVariantType *variant_type;
		int i;

		for (i = 0; i < (int) G_N_ELEMENTS (link_watcher_attr_datas); i++) {
			if (nm_streq (link_watcher_attr_datas[i].dbus_name, v_key)) {
				attr_data = &link_watcher_attr_datas[i];
				break;
			}
		}
		if (!attr_data) {
			if (strict_parsing) {
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				             _("invalid D-Bus property \"%s\""),
				             v_key);
				return NULL;
			}
			continue;
		}

		if (attr_data->value_type == NM_VALUE_TYPE_INT)
			variant_type = G_VARIANT_TYPE_INT32;
		else
			variant_type = nm_value_type_get_variant_type (attr_data->value_type);

		if (!g_variant_is_of_type (v_val, variant_type)) {
			if (strict_parsing) {
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				             _("invalid D-Bus property \"%s\""),
				             v_key);
				return NULL;
			}
			continue;
		}

		if (variants[attr_data->link_watcher_attr]) {
			if (strict_parsing) {
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				             _("duplicate D-Bus property \"%s\""),
				             v_key);
				return NULL;
			}
			g_variant_unref (variants[attr_data->link_watcher_attr]);
		}
		variants[attr_data->link_watcher_attr] = g_steal_pointer (&v_val_free);
	}

#define _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES(_type, _variants, _error, ...) \
	({ \
		int _i; \
		gboolean _has_error = FALSE; \
		\
		for (_i = 0; _i < (int) G_N_ELEMENTS ((_variants)); _i++) { \
			if (   (_variants)[_i] \
			    && !NM_IN_SET ((LinkWatcherAttribute) _i, __VA_ARGS__)) { \
				_has_error = TRUE; \
				g_set_error (_error, \
				             NM_CONNECTION_ERROR, \
				             NM_CONNECTION_ERROR_INVALID_PROPERTY, \
				             _("invalid D-Bus property \"%s\" for \"%s\""), \
				             link_watcher_attr_datas[_i].dbus_name, \
				             _type); \
				break; \
			} \
		} \
		\
		_has_error; \
	})

	v_name = _LINK_WATCHER_ATTR_VARGET_STRING (variants, LINK_WATCHER_ATTRIBUTE_NAME);

	if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_ETHTOOL)) {
		if (   strict_parsing
		    && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES (v_name, variants, error, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ETHTOOL))
			return NULL;
		return nm_team_link_watcher_new_ethtool (_LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_DELAY_UP),
		                                         _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_DELAY_DOWN),
		                                         strict_parsing ? error : NULL);
	}

	if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_NSNA_PING)) {
		if (   strict_parsing
		    && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES (v_name, variants, error, _EXPECTED_LINK_WATCHER_ATTRIBUTES_NSNA_PING))
			return NULL;
		return nm_team_link_watcher_new_nsna_ping (_LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_INIT_WAIT),
		                                           _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_INTERVAL),
		                                           _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_MISSED_MAX),
		                                           _LINK_WATCHER_ATTR_VARGET_STRING (variants, LINK_WATCHER_ATTRIBUTE_TARGET_HOST),
		                                           strict_parsing ? error : NULL);
	}

	if (nm_streq0 (v_name, NM_TEAM_LINK_WATCHER_ARP_PING)) {
		NMTeamLinkWatcherArpPingFlags v_flags = NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE;

		if (   strict_parsing
		    && _VARIANTS_HAVE_UNEXPECTED_ATTRIBUTES (v_name, variants, error, _EXPECTED_LINK_WATCHER_ATTRIBUTES_ARP_PING))
			return NULL;

		if (_LINK_WATCHER_ATTR_VARGET_BOOL (variants, LINK_WATCHER_ATTRIBUTE_VALIDATE_ACTIVE))
			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE;
		if (_LINK_WATCHER_ATTR_VARGET_BOOL (variants, LINK_WATCHER_ATTRIBUTE_VALIDATE_INACTIVE))
			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE;
		if (_LINK_WATCHER_ATTR_VARGET_BOOL (variants, LINK_WATCHER_ATTRIBUTE_SEND_ALWAYS))
			v_flags |= NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS;

		return nm_team_link_watcher_new_arp_ping2 (_LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_INIT_WAIT),
		                                           _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_INTERVAL),
		                                           _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_MISSED_MAX),
		                                           _LINK_WATCHER_ATTR_VARGET_INT (variants, LINK_WATCHER_ATTRIBUTE_VLANID),
		                                           _LINK_WATCHER_ATTR_VARGET_STRING (variants, LINK_WATCHER_ATTRIBUTE_TARGET_HOST),
		                                           _LINK_WATCHER_ATTR_VARGET_STRING (variants, LINK_WATCHER_ATTRIBUTE_SOURCE_HOST),
		                                           v_flags,
		                                           strict_parsing ? error : NULL);
	}

	if (strict_parsing) {
		g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
		             _("unknown link-watcher name \"%s\""),
		             v_name);
	}
	return NULL;
}

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

/**
 * _nm_utils_team_link_watchers_to_variant:
 * @link_watchers: (element-type NMTeamLinkWatcher): array of #NMTeamLinkWatcher
 *
 * Utility function to convert a #GPtrArray of #NMTeamLinkWatcher objects
 * representing link watcher configuration for team devices into a #GVariant
 * of type 'aa{sv}' representing an array of link watchers.
 *
 * Returns: (transfer full): a new floating #GVariant representing link watchers.
 **/
GVariant *
_nm_utils_team_link_watchers_to_variant (const GPtrArray *link_watchers)
{
	GVariantBuilder builder;
	guint i;

	g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
	if (link_watchers) {
		for (i = 0; i < link_watchers->len; i++) {
			g_variant_builder_add (&builder,
			                       "@a{sv}",
			                       _link_watcher_to_variant (link_watchers->pdata[i]));
		}
	}
	return g_variant_builder_end (&builder);
}

/**
 * _nm_utils_team_link_watchers_from_variant:
 * @value: a #GVariant of type 'aa{sv}'
 * @strict_parsing: whether to parse strictly or ignore everything invalid.
 * @error: error reason.
 *
 * Utility function to convert a #GVariant representing a list of team link
 * watchers int a #GPtrArray of #NMTeamLinkWatcher objects.
 *
 * Returns: (transfer full) (element-type NMTeamLinkWatcher): a newly allocated
 *   #GPtrArray of #NMTeamLinkWatcher objects.
 *
 * Note that if you provide an @error, then the function can only fail (and return %NULL)
 * or succeed (and not return %NULL). If you don't provide an @error, then the function
 * never returns %NULL.
 **/
GPtrArray *
_nm_utils_team_link_watchers_from_variant (GVariant *value,
                                           gboolean strict_parsing,
                                           GError **error)
{
	gs_unref_ptrarray GPtrArray *link_watchers = NULL;
	GVariantIter iter;
	GVariant *watcher_var;

	g_return_val_if_fail (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")), NULL);

	link_watchers = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);

	g_variant_iter_init (&iter, value);
	while (g_variant_iter_next (&iter, "@a{sv}", &watcher_var)) {
		_nm_unused gs_unref_variant GVariant *watcher_var_free = watcher_var;
		NMTeamLinkWatcher *watcher;

		watcher = _link_watcher_from_variant (watcher_var, strict_parsing, error);
		if (error && *error)
			return NULL;
		if (watcher)
			g_ptr_array_add (link_watchers, watcher);
	}

	return g_steal_pointer (&link_watchers);
}

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

const char *
nm_team_setting_config_get (const NMTeamSetting *self)
{
	char *js_str;

	nm_assert (self);

	if (G_LIKELY (!self->d._js_str_need_synthetize))
		return self->d._js_str;

	nm_assert (!self->d._js_str);
	nm_assert (self->d.strict_validated);

	if (_team_setting_check_default (self) == 0) {
		/* the default is set. We signal this as a NULL JSON string.
		 * Nothing to do. */
		js_str = NULL;
	} else {
		gboolean list_is_empty = TRUE;
		GString *gstr;

		gstr = g_string_new (NULL);

		g_string_append (gstr, "{ ");

		if (self->d.is_port) {
			static const NMTeamAttribute attr_lst_port[] = {
				NM_TEAM_ATTRIBUTE_PORT_QUEUE_ID,
				NM_TEAM_ATTRIBUTE_PORT_PRIO,
				NM_TEAM_ATTRIBUTE_PORT_STICKY,
				NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO,
				NM_TEAM_ATTRIBUTE_PORT_LACP_KEY,
			};

			if (_team_setting_fields_to_json_maybe (self, gstr, !list_is_empty, attr_lst_port, G_N_ELEMENTS (attr_lst_port)))
				list_is_empty = FALSE;
		} else {
			static const NMTeamAttribute attr_lst_runner_pt1[] = {
				NM_TEAM_ATTRIBUTE_MASTER_RUNNER,
				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_HWADDR_POLICY,
				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH,
			};
			static const NMTeamAttribute attr_lst_runner_pt2[] = {
				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER,
				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_BALANCER_INTERVAL,
			};
			static const NMTeamAttribute attr_lst_runner_pt3[] = {
				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_ACTIVE,
				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_FAST_RATE,
				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_SYS_PRIO,
				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_MIN_PORTS,
				NM_TEAM_ATTRIBUTE_MASTER_RUNNER_AGG_SELECT_POLICY,
			};
			static const NMTeamAttribute attr_lst_notify_peers[] = {
				NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_COUNT,
				NM_TEAM_ATTRIBUTE_MASTER_NOTIFY_PEERS_INTERVAL,
			};
			static const NMTeamAttribute attr_lst_mcast_rejoin[] = {
				NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_COUNT,
				NM_TEAM_ATTRIBUTE_MASTER_MCAST_REJOIN_INTERVAL,
			};

			if (   _team_setting_has_fields_any_v (self, attr_lst_runner_pt1, G_N_ELEMENTS (attr_lst_runner_pt1))
			    || _team_setting_has_fields_any_v (self, attr_lst_runner_pt2, G_N_ELEMENTS (attr_lst_runner_pt2))
			    || _team_setting_has_fields_any_v (self, attr_lst_runner_pt3, G_N_ELEMENTS (attr_lst_runner_pt3))) {
				gboolean list_is_empty2 = TRUE;

				nm_assert (list_is_empty);

				nm_json_aux_gstr_append_obj_name (gstr, "runner", '{');

				if (_team_setting_fields_to_json_maybe (self, gstr, !list_is_empty2, attr_lst_runner_pt1, G_N_ELEMENTS (attr_lst_runner_pt1)))
					list_is_empty2 = FALSE;

				if (_team_setting_has_fields_any_v (self, attr_lst_runner_pt2, G_N_ELEMENTS (attr_lst_runner_pt2))) {
					if (!list_is_empty2)
						nm_json_aux_gstr_append_delimiter (gstr);
					nm_json_aux_gstr_append_obj_name (gstr, "tx_balancer", '{');
					if (!_team_setting_fields_to_json_maybe (self, gstr, FALSE, attr_lst_runner_pt2, G_N_ELEMENTS (attr_lst_runner_pt2)))
						nm_assert_not_reached ();
					g_string_append (gstr, " }");
					list_is_empty2 = FALSE;
				}

				if (_team_setting_fields_to_json_maybe (self, gstr, !list_is_empty2, attr_lst_runner_pt3, G_N_ELEMENTS (attr_lst_runner_pt3)))
					list_is_empty2 = FALSE;

				nm_assert (!list_is_empty2);
				g_string_append (gstr, " }");
				list_is_empty = FALSE;
			}

			if (_team_setting_has_fields_any_v (self, attr_lst_notify_peers, G_N_ELEMENTS (attr_lst_notify_peers))) {
				if (!list_is_empty)
					nm_json_aux_gstr_append_delimiter (gstr);
				nm_json_aux_gstr_append_obj_name (gstr, "notify_peers", '{');
				if (!_team_setting_fields_to_json_maybe (self, gstr, FALSE, attr_lst_notify_peers, G_N_ELEMENTS (attr_lst_notify_peers)))
					nm_assert_not_reached ();
				g_string_append (gstr, " }");
				list_is_empty = FALSE;
			}

			if (_team_setting_has_fields_any_v (self, attr_lst_mcast_rejoin, G_N_ELEMENTS (attr_lst_mcast_rejoin))) {
				if (!list_is_empty)
					nm_json_aux_gstr_append_delimiter (gstr);
				nm_json_aux_gstr_append_obj_name (gstr, "mcast_rejoin", '{');
				if (!_team_setting_fields_to_json_maybe (self, gstr, FALSE, attr_lst_mcast_rejoin, G_N_ELEMENTS (attr_lst_mcast_rejoin)))
					nm_assert_not_reached ();
				g_string_append (gstr, " }");
				list_is_empty = FALSE;
			}
		}

		if (_team_setting_field_to_json (self,
		                                 gstr,
		                                 !list_is_empty,
		                                 _team_attr_data_get (self->d.is_port, NM_TEAM_ATTRIBUTE_LINK_WATCHERS)))
			list_is_empty = FALSE;

		if (!list_is_empty)
			g_string_append (gstr, " }");

		js_str = g_string_free (gstr, list_is_empty);;
	}

	/* mutate the constant object. In C++ speak, these fields are "mutable".
	 * That is because we construct the JSON string lazily/on-demand. */
	*((char **) &self->_data_priv._js_str) = js_str;
	*((bool *) &self->_data_priv._js_str_need_synthetize) = FALSE;

	return self->d._js_str;
}

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

#if WITH_JSON_VALIDATION
static gboolean
_attr_data_match_keys (const TeamAttrData *attr_data,
                       const char *const*keys,
                       guint8 n_keys)
{
	guint8 i;

	_team_attr_data_ASSERT (attr_data);
	nm_assert (keys);
	nm_assert (n_keys > 0);
	nm_assert (({
		gboolean all_non_null = TRUE;

		for (i = 0; i < n_keys; i++)
			all_non_null = all_non_null && keys[i] && keys[i][0] != '\0';
		all_non_null;
	}));

	if (attr_data->js_keys_len < n_keys)
		return FALSE;
	for (i = 0; i < n_keys; i++) {
		if (!nm_streq (keys[i], attr_data->js_keys[i]))
			return FALSE;
	}
	return TRUE;
}

static const TeamAttrData *
_attr_data_find_by_json_key (gboolean is_port,
                             const char *const*keys,
                             guint8 n_keys)
{
	const TeamAttrData *attr_data;

	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
		if (    _team_attr_data_is_relevant (attr_data, is_port)
		    && _attr_data_match_keys (attr_data, keys, n_keys))
			return attr_data;
	}

	return NULL;
}

static void
_js_parse_locate_keys (NMTeamSetting *self,
                       json_t *root_js_obj,
                       json_t *found_keys[static _NM_TEAM_ATTRIBUTE_NUM],
                       gboolean *out_unrecognized_content)
{
	const char *keys[3];
	const char *cur_key1;
	const char *cur_key2;
	const char *cur_key3;
	json_t *cur_val1;
	json_t *cur_val2;
	json_t *cur_val3;

#define _handle(_self, _cur_key, _cur_val, _keys, _level, _found_keys, _out_unrecognized_content) \
	({ \
		const TeamAttrData *_attr_data; \
		gboolean _handled = FALSE; \
		\
		(_keys)[(_level) - 1] = (_cur_key); \
		_attr_data = _attr_data_find_by_json_key ((_self)->d.is_port, (_keys), (_level)); \
		if (   _attr_data \
			&& _attr_data->js_keys_len == (_level)) { \
			if ((_found_keys)[_attr_data->team_attr]) \
				*(_out_unrecognized_content) = TRUE; \
			(_found_keys)[_attr_data->team_attr] = (_cur_val); \
			_handled = TRUE; \
		} else if (   !_attr_data \
		           || !json_is_object ((_cur_val))) { \
			*(_out_unrecognized_content) = TRUE; \
			_handled = TRUE; \
		} \
		_handled; \
	})

	json_object_foreach (root_js_obj, cur_key1, cur_val1) {
		if (!_handle (self, cur_key1, cur_val1, keys, 1, found_keys, out_unrecognized_content)) {
			json_object_foreach (cur_val1, cur_key2, cur_val2) {
				if (!_handle (self, cur_key2, cur_val2, keys, 2, found_keys, out_unrecognized_content)) {
					json_object_foreach (cur_val2, cur_key3, cur_val3) {
						if (!_handle (self, cur_key3, cur_val3, keys, 3, found_keys, out_unrecognized_content))
							*out_unrecognized_content = TRUE;
					}
				}
			}
		}
	}

#undef _handle
}

static void
_js_parse_unpack (gboolean is_port,
                  json_t *found_keys[static _NM_TEAM_ATTRIBUTE_NUM],
                  bool out_has_lst[static _NM_TEAM_ATTRIBUTE_NUM],
                  NMValueTypUnion out_val_lst[static _NM_TEAM_ATTRIBUTE_NUM],
                  gboolean *out_unrecognized_content,
                  GPtrArray **out_ptr_array_link_watchers_free,
                  GPtrArray **out_ptr_array_master_runner_tx_hash_free)
{
	const TeamAttrData *attr_data;

	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
		NMValueTypUnion *p_out_val;
		gboolean valid = FALSE;
		json_t *arg_js_obj;

		if (!_team_attr_data_is_relevant (attr_data, is_port))
			continue;

		nm_assert (!out_has_lst[attr_data->team_attr]);

		arg_js_obj = found_keys[attr_data->team_attr];
		if (!arg_js_obj)
			continue;

		p_out_val = &out_val_lst[attr_data->team_attr];

		if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC)
			valid = nm_value_type_from_json (attr_data->value_type, arg_js_obj, p_out_val);
		else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) {
			GPtrArray *link_watchers = NULL;
			NMTeamLinkWatcher *link_watcher;

			nm_assert (out_ptr_array_link_watchers_free && !*out_ptr_array_link_watchers_free);
			if (json_is_array (arg_js_obj)) {
				gsize i, len;

				len = json_array_size (arg_js_obj);
				link_watchers = g_ptr_array_new_full (len, (GDestroyNotify) nm_team_link_watcher_unref);
				for (i = 0; i < len; i++) {
					link_watcher = _link_watcher_from_json (json_array_get (arg_js_obj, i),
					                                        out_unrecognized_content);
					if (link_watcher)
						g_ptr_array_add (link_watchers, link_watcher);
				}
			} else {
				link_watcher = _link_watcher_from_json (arg_js_obj,
				                                        out_unrecognized_content);
				if (link_watcher) {
					link_watchers = g_ptr_array_new_full (1, (GDestroyNotify) nm_team_link_watcher_unref);
					g_ptr_array_add (link_watchers, link_watcher);
				}
			}
			if (link_watchers) {
				valid = TRUE;
				p_out_val->v_ptrarray = link_watchers;
				*out_ptr_array_link_watchers_free = link_watchers;
			}
		} else if (   !is_port
		           && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
			GPtrArray *strv = NULL;

			nm_assert (out_ptr_array_master_runner_tx_hash_free && !*out_ptr_array_master_runner_tx_hash_free);
			if (json_is_array (arg_js_obj)) {
				gsize i, len;

				len = json_array_size (arg_js_obj);
				if (len > 0) {
					strv = g_ptr_array_sized_new (len);
					for (i = 0; i < len; i++) {
						const char *v_string;

						if (   nm_jansson_json_as_string (json_array_get (arg_js_obj, i),
						                                  &v_string) <= 0
						    || !v_string
						    || v_string[0] == '\0') {
							/* we remember that there was some invalid content, but parts of the
							 * list could still be parsed. */
							*out_unrecognized_content = TRUE;
							continue;
						}
						g_ptr_array_add (strv, (char *) v_string);
					}
				}
				valid = TRUE;
				*out_ptr_array_master_runner_tx_hash_free = strv;
			}
			p_out_val->v_ptrarray = strv;
		} else
			nm_assert_not_reached ();

		out_has_lst[attr_data->team_attr] = valid;
		if (!valid)
			*out_unrecognized_content = TRUE;
	}
}
#endif

guint32
nm_team_setting_config_set (NMTeamSetting *self, const char *js_str)
{
	guint32 changed_flags = 0;
	gboolean do_set_default = TRUE;
	gboolean new_js_str_invalid = FALSE;

	_team_setting_ASSERT (self);

	if (   !js_str
	    || js_str[0] == '\0') {
		changed_flags = _team_setting_set_default (self);
		if (   changed_flags != 0
		    || !nm_streq0 (js_str, self->d._js_str))
			changed_flags |= nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);
		nm_clear_g_free ((char **) &self->_data_priv._js_str);
		self->_data_priv._js_str = g_strdup (js_str);
		self->_data_priv._js_str_need_synthetize = FALSE;
		self->_data_priv.strict_validated = TRUE;
		self->_data_priv.js_str_invalid = FALSE;
		return changed_flags;
	}

	if (   self->d._js_str
	    && nm_streq (js_str, self->d._js_str)) {
	    if (!self->d.strict_validated) {
			/* setting the same JSON string twice in a row has no effect. */
			return 0;
		}
	} else
		changed_flags |= nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);

#if WITH_JSON_VALIDATION
	{
		nm_auto_decref_json json_t *root_js_obj = NULL;

		if (nm_jansson_load ())
			root_js_obj = json_loads (js_str, 0, NULL);

		if (   !root_js_obj
		    || !json_is_object (root_js_obj))
			new_js_str_invalid = TRUE;
		else {
			gboolean unrecognized_content = FALSE;
			bool has_lst[_NM_TEAM_ATTRIBUTE_NUM] = { FALSE, };
			NMValueTypUnion val_lst[_NM_TEAM_ATTRIBUTE_NUM];
			json_t *found_keys[_NM_TEAM_ATTRIBUTE_NUM] = { NULL, };
			gs_unref_ptrarray GPtrArray *ptr_array_master_runner_tx_hash_free = NULL;
			gs_unref_ptrarray GPtrArray *ptr_array_link_watchers_free = NULL;

			_js_parse_locate_keys (self,
			                       root_js_obj,
			                       found_keys,
			                       &unrecognized_content);

			_js_parse_unpack (self->d.is_port,
			                  found_keys,
			                  has_lst,
			                  val_lst,
			                  &unrecognized_content,
			                  &ptr_array_link_watchers_free,
			                  &ptr_array_master_runner_tx_hash_free);

			do_set_default = FALSE;

			changed_flags |= _team_setting_set (self,
			                                    TRUE,
			                                    has_lst,
			                                    val_lst);
		}
	}

#endif

	if (do_set_default)
		changed_flags |= _team_setting_set_default (self);

	self->_data_priv.strict_validated = FALSE;
	self->_data_priv._js_str_need_synthetize = FALSE;
	self->_data_priv.js_str_invalid = new_js_str_invalid;
	g_free ((char *) self->_data_priv._js_str);
	self->_data_priv._js_str = g_strdup (js_str);

	return changed_flags;
}

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

static void
_team_setting_prefix_error_plain (gboolean is_port,
                                  const char *property_name,
                                  GError **error)
{
	g_prefix_error (error,
	                "%s.%s: ",
	                  is_port
	                ? NM_SETTING_TEAM_PORT_SETTING_NAME
	                : NM_SETTING_TEAM_SETTING_NAME,
	                property_name);
}

static void
_team_setting_prefix_error (const NMTeamSetting *self,
                            const char *prop_name_master,
                            const char *prop_name_port,
                            GError **error)
{
	_team_setting_ASSERT (self);
	nm_assert (  self->d.is_port
	           ? (!!prop_name_port)
	           : (!!prop_name_master));
	_team_setting_prefix_error_plain (self->d.is_port,
	                                    self->d.is_port
	                                  ? prop_name_port
	                                  : prop_name_master,
	                                  error);
}

static gboolean
_team_setting_verify_properties (const NMTeamSetting *self,
                                 GError **error)
{
	const TeamAttrData *attr_data;
	guint i;

	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {

		if (!_team_attr_data_is_relevant (attr_data, self->d.is_port))
			continue;
		if (!_team_setting_has_field (self, attr_data))
			continue;

		if (attr_data->has_range) {
			gconstpointer p_field;

			p_field = _team_setting_get_field (self, attr_data);
			if (attr_data->value_type == NM_VALUE_TYPE_INT32) {
				gint32 v = *((const gint32 *) p_field);

				if (   v < attr_data->range.r_int32.min
				    || v > attr_data->range.r_int32.max) {
					g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
					             _("value out or range"));
					_team_setting_prefix_error_plain (self->d.is_port, attr_data->property_name, error);
					return FALSE;
				}
			} else if (attr_data->value_type == NM_VALUE_TYPE_STRING) {
				const char *v = *((const char *const*) p_field);

				if (nm_utils_strv_find_first ((char **) attr_data->range.r_string.valid_names,
				                              -1,
				                              v) < 0) {
					g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
					             _("invalid value"));
					_team_setting_prefix_error_plain (self->d.is_port, attr_data->property_name, error);
					return FALSE;
				}
			} else
				nm_assert_not_reached ();
		}

		if (   !self->d.is_port
		    && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
			if (self->d.master.runner_tx_hash) {
				for (i = 0; i < self->d.master.runner_tx_hash->len; i++) {
					const char *val = self->d.master.runner_tx_hash->pdata[i];

					if (  !val
					    || (nm_utils_strv_find_first ((char **) _valid_names_runner_tx_hash,
					                                  -1,
					                                  val) < 0)) {
						g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
						             _("invalid runner-tx-hash"));
						_team_setting_prefix_error_plain (self->d.is_port, NM_SETTING_TEAM_RUNNER_TX_HASH, error);
						return FALSE;
					}
				}
			}
		}
	}

	if (!self->d.is_port) {

		for (i = 0; i < G_N_ELEMENTS (_runner_compat_lst); i++) {
			const RunnerCompatElem *e = &_runner_compat_lst[i];

			nm_assert (NM_PTRARRAY_LEN (e->valid_runners) > 0);

			attr_data = _team_attr_data_get (FALSE, e->team_attr);

			if (!_team_setting_has_field (self, attr_data))
				continue;
			if (   self->d.master.runner
			    && (nm_utils_strv_find_first ((char **) e->valid_runners,
			                                  -1,
			                                  self->d.master.runner) >= 0))
				continue;
			if (e->valid_runners[1] == NULL) {
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
				             _("%s is only allowed for runner %s"),
				             attr_data->property_name,
				             e->valid_runners[0]);
			} else {
				gs_free char *s = NULL;

				s = g_strjoinv (",", (char **) e->valid_runners);
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
				             _("%s is only allowed for runners %s"),
				             attr_data->property_name,
				             s);
			}
			_team_setting_prefix_error_plain (self->d.is_port, NM_SETTING_TEAM_RUNNER, error);
			return FALSE;
		}
	} else {
		gboolean has_lacp_attrs;
		gboolean has_activebackup_attrs;

		has_lacp_attrs = _team_setting_has_fields_any (self, NM_TEAM_ATTRIBUTE_PORT_LACP_PRIO,
		                                                     NM_TEAM_ATTRIBUTE_PORT_LACP_KEY);
		has_activebackup_attrs = _team_setting_has_fields_any (self, NM_TEAM_ATTRIBUTE_PORT_PRIO,
		                                                             NM_TEAM_ATTRIBUTE_PORT_STICKY);
		if (has_lacp_attrs && has_activebackup_attrs) {
			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
			             _("cannot set parameters for lacp and activebackup runners together"));
			_team_setting_prefix_error (self, NM_SETTING_TEAM_LINK_WATCHERS, NM_SETTING_TEAM_PORT_LINK_WATCHERS, error);
			return FALSE;
		}
	}

	for (i = 0; i < self->d.link_watchers->len; i++) {
		if (!self->d.link_watchers->pdata[i]) {
			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_SETTING,
			             _("missing link watcher"));
			_team_setting_prefix_error (self, NM_SETTING_TEAM_LINK_WATCHERS, NM_SETTING_TEAM_PORT_LINK_WATCHERS, error);
			return FALSE;
		}
	}

	return TRUE;
}

static gboolean
_team_setting_verify_config (const NMTeamSetting *self,
                             GError **error)
{
	const char *js_str;

	/* we always materialize the JSON string. That is because we want to validate the
	 * string length of the resulting JSON. */
	js_str = nm_team_setting_config_get (self);

	if (js_str) {
		if (strlen (js_str) > 1*1024*1024) {
			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
			             _("team config exceeds size limit"));
			_team_setting_prefix_error (self, NM_SETTING_TEAM_CONFIG, NM_SETTING_TEAM_PORT_CONFIG, error);
			return FALSE;
		}
		if (!g_utf8_validate (js_str, -1, NULL)) {
			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
			             _("team config is not valid UTF-8"));
			_team_setting_prefix_error (self, NM_SETTING_TEAM_CONFIG, NM_SETTING_TEAM_PORT_CONFIG, error);
			return FALSE;
		}
		if (self->d.js_str_invalid) {
			g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
			             _("invalid json"));
			_team_setting_prefix_error (self, NM_SETTING_TEAM_CONFIG, NM_SETTING_TEAM_PORT_CONFIG, error);
			return FALSE;
		}
	}

	return TRUE;
}

gboolean
nm_team_setting_verify (const NMTeamSetting *self,
                        GError **error)
{
	if (self->d.strict_validated) {
		if (!_team_setting_verify_properties (self, error))
			return FALSE;
	}
	return _team_setting_verify_config (self, error);
}

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

int
nm_team_setting_cmp (const NMTeamSetting *self_a,
                     const NMTeamSetting *self_b,
                     gboolean ignore_js_str)
{
	const TeamAttrData *attr_data;

	NM_CMP_SELF (self_a, self_b);

	NM_CMP_FIELD_UNSAFE (self_a, self_b, d.is_port);

	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
		if (!_team_attr_data_is_relevant (attr_data, self_a->d.is_port))
			continue;

		NM_CMP_RETURN (_team_attr_data_cmp (attr_data,
		                                    self_a->d.is_port,
		                                    _team_setting_get_field (self_a, attr_data),
		                                    _team_setting_get_field (self_b, attr_data)));
	}

	if (!ignore_js_str) {
		NM_CMP_DIRECT_STRCMP0 (nm_team_setting_config_get (self_a),
		                       nm_team_setting_config_get (self_b));
	}

	return 0;
}

guint32
nm_team_setting_reset (NMTeamSetting *self,
                       const NMTeamSetting *src)
{
	const TeamAttrData *attr_data;
	guint32 changed_flags;

	_team_setting_ASSERT (self);
	_team_setting_ASSERT (src);
	nm_assert (self->d.is_port == src->d.is_port);

	if (self == src)
		return 0;

	changed_flags = 0;

	for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
		if (!_team_attr_data_is_relevant (attr_data, self->d.is_port))
			continue;
		if (_team_attr_data_equal (attr_data,
		                           self->d.is_port,
		                           _team_setting_get_field (self, attr_data),
		                           _team_setting_get_field (src, attr_data)))
			continue;
		_team_attr_data_copy (attr_data,
		                      self->d.is_port,
		                      _team_setting_get_field (self, attr_data),
		                      _team_setting_get_field (src, attr_data));
		changed_flags |= nm_team_attribute_to_flags (attr_data->team_attr);
	}

	self->_data_priv.has_fields_mask = src->d.has_fields_mask;

	if (!nm_streq0 (self->d._js_str, src->d._js_str)) {
		g_free ((char *) self->_data_priv._js_str);
		self->_data_priv._js_str = g_strdup (src->d._js_str);
		changed_flags |= nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);
	} else if (changed_flags != 0)
		changed_flags |= nm_team_attribute_to_flags (NM_TEAM_ATTRIBUTE_CONFIG);

	self->_data_priv._js_str_need_synthetize = src->d._js_str_need_synthetize;
	self->_data_priv.strict_validated = src->d.strict_validated;
	self->_data_priv.js_str_invalid = src->d.js_str_invalid;

	return changed_flags;
}

static void
_variants_list_team_unref_auto (GVariant *(*p_variants)[])
{
	int i;

	for (i = 0; i < _NM_TEAM_ATTRIBUTE_NUM; i++)
		nm_g_variant_unref ((*p_variants)[i]);
}

gboolean
nm_team_setting_reset_from_dbus (NMTeamSetting *self,
                                 GVariant *setting_dict,
                                 GHashTable *keys,
                                 guint32 *out_changed,
                                 guint /* NMSettingParseFlags */ parse_flags,
                                 GError **error)
{
	nm_auto (_variants_list_team_unref_auto) GVariant *variants[_NM_TEAM_ATTRIBUTE_NUM] = { NULL, };
	gs_unref_ptrarray GPtrArray *v_link_watchers = NULL;
	const TeamAttrData *attr_data;
	GVariantIter iter;
	const char *v_key;
	GVariant *v_val;

	*out_changed = 0;

	g_variant_iter_init (&iter, setting_dict);
	while (g_variant_iter_next (&iter, "{&sv}", &v_key, &v_val)) {
		_nm_unused gs_unref_variant GVariant *v_val_free = v_val;
		const GVariantType *variant_type = NULL;

		attr_data = _team_attr_data_find_for_property_name (self->d.is_port, v_key);
		if (!attr_data) {
			/* _nm_setting_new_from_dbus() already checks for unknown keys. Don't
			 * do that here. */
			continue;
		}

		if (keys)
			g_hash_table_remove (keys, v_key);

		if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC)
			variant_type = nm_value_type_get_variant_type (attr_data->value_type);
		else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_CONFIG)
			variant_type = G_VARIANT_TYPE_STRING;
		else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS)
			variant_type = G_VARIANT_TYPE ("aa{sv}");
		else if (   !self->d.is_port
		         && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH)
			variant_type = G_VARIANT_TYPE_STRING_ARRAY;
		else
			nm_assert_not_reached ();

		if (!g_variant_is_of_type (v_val, variant_type)) {
			if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				             _("invalid D-Bus type \"%s\""),
				             g_variant_get_type_string (v_val));
				_team_setting_prefix_error_plain (self->d.is_port,
				                                  attr_data->property_name,
				                                  error);
				return FALSE;
			}
			continue;
		}

		/* _nm_setting_new_from_dbus() already checks for duplicate keys. Don't
		 * do that here. */
		nm_g_variant_unref (variants[attr_data->team_attr]);
		variants[attr_data->team_attr] = g_steal_pointer (&v_val_free);
	}

	if (variants[NM_TEAM_ATTRIBUTE_LINK_WATCHERS]) {

		if (   variants[NM_TEAM_ATTRIBUTE_CONFIG]
		    && WITH_JSON_VALIDATION
		    && !NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
			/* we don't require the content of the "link-watchers" and we also
			 * don't perform strict validation. No need to parse it. */
		} else {
			gs_free_error GError *local = NULL;

			/* We might need the parsed v_link_watchers array below (because there is no JSON
			 * "config" present or because we don't build WITH_JSON_VALIDATION).
			 *
			 * Or we might run with NM_SETTING_PARSE_FLAGS_STRICT. In that mode, we may not necessarily
			 * require that the entire setting as a whole validates (if a JSON config is present and
			 * we are not "strict_validated") , but we require that we can at least parse the link watchers
			 * on their own. */
			v_link_watchers = _nm_utils_team_link_watchers_from_variant (variants[NM_TEAM_ATTRIBUTE_LINK_WATCHERS],
			                                                             NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT),
			                                                             &local);
			if (   local
			    && NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
				g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
				             _("invalid link-watchers: %s"),
				             local->message);
				_team_setting_prefix_error (self,
				                            NM_SETTING_TEAM_LINK_WATCHERS,
				                            NM_SETTING_TEAM_PORT_LINK_WATCHERS,
				                            error);
				return FALSE;
			}
		}
	}

	*out_changed |= nm_team_setting_config_set (self,
	                                              variants[NM_TEAM_ATTRIBUTE_CONFIG]
	                                            ? g_variant_get_string (variants[NM_TEAM_ATTRIBUTE_CONFIG], NULL)
	                                            : NULL);

	if (   WITH_JSON_VALIDATION
	    && variants[NM_TEAM_ATTRIBUTE_CONFIG]) {
		/* for team settings, the JSON must be able to express all possible options. That means,
		 * if the GVariant contains both the JSON "config" and other options, then the other options
		 * are silently ignored. */
	} else {
		guint32 extra_changed = 0u;

		for (attr_data = &team_attr_datas[TEAM_ATTR_IDX_CONFIG + 1]; attr_data < &team_attr_datas[G_N_ELEMENTS (team_attr_datas)]; attr_data++) {
			NMValueTypUnion val;
			guint32 changed_flags = 0u;

			if (!_team_attr_data_is_relevant (attr_data, self->d.is_port))
				continue;
			if (!variants[attr_data->team_attr])
				continue;

			if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) {
				nm_value_type_get_from_variant (attr_data->value_type, &val, variants[attr_data->team_attr], FALSE);
				changed_flags = _team_setting_value_set (self,
				                                         attr_data,
				                                         &val,
				                                         SET_FIELD_MODE_SET,
				                                         RESET_JSON_NO);
			} else if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS) {
				changed_flags = _team_setting_value_link_watchers_set_list (self,
				                                                            v_link_watchers ? (const NMTeamLinkWatcher *const *) v_link_watchers->pdata : NULL,
				                                                            v_link_watchers ? v_link_watchers->len : 0u,
				                                                            SET_FIELD_MODE_SET,
				                                                            RESET_JSON_NO);
			} else if (   !self->d.is_port
			           && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
				gs_free const char **strv = NULL;
				gsize len;

				strv = g_variant_get_strv (variants[attr_data->team_attr], &len);
				changed_flags = _team_setting_value_master_runner_tx_hash_set_list (self,
				                                                                    strv,
				                                                                    NM_MIN (len, (gsize) G_MAXUINT),
				                                                                    SET_FIELD_MODE_SET,
				                                                                    RESET_JSON_NO);
			} else
				nm_assert_not_reached ();

			extra_changed |= changed_flags;
		}

		if (!variants[NM_TEAM_ATTRIBUTE_CONFIG]) {
			/* clear the JSON string so it can be regenerated. But only if we didn't set
			 * it above. */
			self->_data_priv.strict_validated = TRUE;
			self->_data_priv._js_str_need_synthetize = TRUE;
		}

		*out_changed |= extra_changed;
	}

	return TRUE;
}

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

gboolean
nm_team_setting_maybe_changed (NMSetting *source,
                               const GParamSpec *const*obj_properties,
                               guint32 changed_flags)
{
	NMTeamAttribute team_attr;
	int count_flags;
	guint32 ch;

	if (changed_flags == 0u)
		return FALSE;

	count_flags = 0;
	for (ch = changed_flags; ch != 0u; ch >>= 1) {
		if (NM_FLAGS_HAS (ch, 0x1u))
			count_flags++;
	}

	if (count_flags > 1)
		g_object_freeze_notify (G_OBJECT (source));

	ch = changed_flags;
	for (team_attr = 0; team_attr < _NM_TEAM_ATTRIBUTE_NUM; team_attr++) {
		if (!NM_FLAGS_ANY (ch, nm_team_attribute_to_flags (team_attr)))
			continue;
		g_object_notify_by_pspec (G_OBJECT (source),
		                          (GParamSpec *) obj_properties[team_attr]);
		ch &= ~nm_team_attribute_to_flags (team_attr);
		if (ch == 0)
			break;
	}

	if (count_flags > 1)
		g_object_thaw_notify (G_OBJECT (source));

	return TRUE;
}

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

NMTeamSetting *
_nm_setting_get_team_setting (struct _NMSetting *setting)
{
	if (NM_IS_SETTING_TEAM (setting))
		return _nm_setting_team_get_team_setting (NM_SETTING_TEAM (setting));
	return _nm_setting_team_port_get_team_setting (NM_SETTING_TEAM_PORT (setting));
}

static GVariant *
_nm_team_settings_property_to_dbus (const NMSettInfoSetting *sett_info,
                                    guint property_idx,
                                    NMConnection *connection,
                                    NMSetting *setting,
                                    NMConnectionSerializationFlags flags,
                                    const NMConnectionSerializationOptions *options)
{
	NMTeamSetting *self = _nm_setting_get_team_setting (setting);
	const TeamAttrData *attr_data = _team_attr_data_get (self->d.is_port, sett_info->property_infos[property_idx].param_spec->param_id);

	if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_CONFIG) {
		const char *config;

		if (   self->d.strict_validated
		    && !_nm_utils_is_manager_process) {
			/* if we are in strict validating mode on the client side, the JSON is generated
			 * artificially. In this case, don't send the config via D-Bus to the server.
			 *
			 * This also will cause NetworkManager to strictly validate the settings.
			 * If a JSON "config" is present, strict validation won't be performed. */
			return NULL;
		}

		config = nm_team_setting_config_get (self);
		return config ? g_variant_new_string (config) : NULL;
	}

	if (!_team_setting_has_field (self, attr_data))
		return NULL;

	if (attr_data->value_type != NM_VALUE_TYPE_UNSPEC) {
		return nm_value_type_to_variant (attr_data->value_type,
	                                     _team_setting_get_field (self, attr_data));
	}
	if (attr_data->team_attr == NM_TEAM_ATTRIBUTE_LINK_WATCHERS)
		return _nm_utils_team_link_watchers_to_variant (self->d.link_watchers);
	if (   !self->d.is_port
	    && attr_data->team_attr == NM_TEAM_ATTRIBUTE_MASTER_RUNNER_TX_HASH) {
		return g_variant_new_strv (self->d.master.runner_tx_hash ? (const char *const*) self->d.master.runner_tx_hash->pdata : NULL,
		                           self->d.master.runner_tx_hash ? self->d.master.runner_tx_hash->len : 0u);
	}

	nm_assert_not_reached ();
	return NULL;
}

static void
_nm_team_settings_property_from_dbus_link_watchers (GVariant *dbus_value,
                                                    GValue *prop_value)
{
	g_value_take_boxed (prop_value,
	                    _nm_utils_team_link_watchers_from_variant (dbus_value, FALSE, NULL));
}

const NMSettInfoPropertType nm_sett_info_propert_type_team_b = {
	.dbus_type           = G_VARIANT_TYPE_BOOLEAN,
	.to_dbus_fcn         = _nm_team_settings_property_to_dbus,
};

const NMSettInfoPropertType nm_sett_info_propert_type_team_i = {
	.dbus_type           = G_VARIANT_TYPE_INT32,
	.to_dbus_fcn         = _nm_team_settings_property_to_dbus,
};

const NMSettInfoPropertType nm_sett_info_propert_type_team_s = {
	.dbus_type           = G_VARIANT_TYPE_STRING,
	.to_dbus_fcn         = _nm_team_settings_property_to_dbus,
};

const NMSettInfoPropertType nm_sett_info_propert_type_team_as = {
	.dbus_type           = NM_G_VARIANT_TYPE ("as"),
	.to_dbus_fcn         = _nm_team_settings_property_to_dbus,
};

const NMSettInfoPropertType nm_sett_info_propert_type_team_link_watchers = {
	.dbus_type           = NM_G_VARIANT_TYPE ("aa{sv}"),
	.to_dbus_fcn         = _nm_team_settings_property_to_dbus,
	.gprop_from_dbus_fcn = _nm_team_settings_property_from_dbus_link_watchers,
};

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

NMTeamSetting *
nm_team_setting_new (gboolean is_port,
                     const char *js_str)
{
	NMTeamSetting *self;
	gsize l;

	G_STATIC_ASSERT_EXPR (sizeof (*self) == sizeof (self->_data_priv));
	G_STATIC_ASSERT_EXPR (sizeof (*self) == NM_CONST_MAX (nm_offsetofend (NMTeamSetting, d.master), nm_offsetofend (NMTeamSetting, d.port)));

	l =   is_port
	    ? nm_offsetofend (NMTeamSetting, d.port)
	    : nm_offsetofend (NMTeamSetting, d.master);

	self = g_malloc0 (l);

	self->_data_priv.is_port                 = is_port;
	self->_data_priv.strict_validated        = TRUE;
	self->_data_priv._js_str_need_synthetize = FALSE;
	self->_data_priv.link_watchers           = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_team_link_watcher_unref);

	_team_setting_ASSERT (self);

	nm_team_setting_config_set (self, js_str);

	_team_setting_ASSERT (self);

	return self;
}

void
nm_team_setting_free (NMTeamSetting *self)
{
	if (!self)
		return;

	_team_setting_ASSERT (self);

	if (!self->d.is_port) {
		nm_clear_pointer (((GPtrArray **) &self->_data_priv.master.runner_tx_hash), g_ptr_array_unref);
		g_free ((char *) self->_data_priv.master.runner);
		g_free ((char *) self->_data_priv.master.runner_hwaddr_policy);
		g_free ((char *) self->_data_priv.master.runner_tx_balancer);
		g_free ((char *) self->_data_priv.master.runner_agg_select_policy);
	}
	g_ptr_array_unref ((GPtrArray *) self->_data_priv.link_watchers);
	g_free ((char *) self->_data_priv._js_str);
	g_free (self);
}