diff options
Diffstat (limited to 'libnm-core/nm-team-utils.c')
| -rw-r--r-- | libnm-core/nm-team-utils.c | 2542 |
1 files changed, 2542 insertions, 0 deletions
diff --git a/libnm-core/nm-team-utils.c b/libnm-core/nm-team-utils.c new file mode 100644 index 00000000..ac834d74 --- /dev/null +++ b/libnm-core/nm-team-utils.c @@ -0,0 +1,2542 @@ +/* + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA. + * + * Copyright 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; + + if (!list_is_empty) + nm_json_aux_gstr_append_delimiter (gstr); + 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)); +} + +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; +} + +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)); +} + +/*****************************************************************************/ + +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); +} |