/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
 * Copyright (C) 2015 - 2018 Red Hat, Inc.
 */

#include "libnm-glib-aux/nm-default-glib-i18n-lib.h"

#include "nmp-plobj.h"

#include "nm-compat-headers/linux/if_addr.h"

#include "libnm-glib-aux/nm-time-utils.h"
#include "nm-platform-utils.h"

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

#define TO_STRING_IFA_FLAGS_BUF_SIZE 256

static const char *
_to_string_ifa_flags(guint32 ifa_flags, char *buf, gsize size)
{
#define S_FLAGS_PREFIX " flags "
    nm_assert(buf && size >= TO_STRING_IFA_FLAGS_BUF_SIZE && size > NM_STRLEN(S_FLAGS_PREFIX));

    if (!ifa_flags)
        buf[0] = '\0';
    else {
        nm_platform_addr_flags2str(ifa_flags,
                                   &buf[NM_STRLEN(S_FLAGS_PREFIX)],
                                   size - NM_STRLEN(S_FLAGS_PREFIX));
        if (buf[NM_STRLEN(S_FLAGS_PREFIX)] == '\0')
            buf[0] = '\0';
        else
            memcpy(buf, S_FLAGS_PREFIX, NM_STRLEN(S_FLAGS_PREFIX));
    }
    return buf;
}

#define _to_string_dev(arr, ifindex)                                                   \
    ({                                                                                 \
        const int _ifindex = (ifindex);                                                \
                                                                                       \
        _ifindex ? nm_sprintf_buf((arr), " dev %d", ifindex) : nm_str_truncate((arr)); \
    })

static const char *
_lifetime_to_string(guint32 timestamp, guint32 lifetime, gint32 now, char *buf, size_t buf_size)
{
    if (lifetime == NM_PLATFORM_LIFETIME_PERMANENT)
        return "forever";

    g_snprintf(buf,
               buf_size,
               "%usec",
               nmp_utils_lifetime_rebase_relative_time_on_now(timestamp, lifetime, now));
    return buf;
}

static const char *
_lifetime_summary_to_string(gint32  now,
                            guint32 timestamp,
                            guint32 preferred,
                            guint32 lifetime,
                            char   *buf,
                            size_t  buf_size)
{
    g_snprintf(buf,
               buf_size,
               " lifetime %d-%u[%u,%u]",
               (signed) now,
               (unsigned) timestamp,
               (unsigned) preferred,
               (unsigned) lifetime);
    return buf;
}

static int
_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b)
{
    guint32 lifetime_a;
    guint32 lifetime_b;
    guint32 preferred_a;
    guint32 preferred_b;
    gint32  now = 0;

    lifetime_a =
        nmp_utils_lifetime_get(a->timestamp, a->lifetime, a->preferred, &now, &preferred_a);
    lifetime_b =
        nmp_utils_lifetime_get(b->timestamp, b->lifetime, b->preferred, &now, &preferred_b);

    NM_CMP_DIRECT(lifetime_a, lifetime_b);
    NM_CMP_DIRECT(preferred_a, preferred_b);
    return 0;
}

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

void
nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState *h)
{
    nm_hash_update_vals(h,
                        obj->ifindex,
                        obj->addr_source,
                        obj->use_ip4_broadcast_address ? obj->broadcast_address : ((in_addr_t) 0u),
                        obj->timestamp,
                        obj->lifetime,
                        obj->preferred,
                        obj->n_ifa_flags,
                        obj->plen,
                        obj->address,
                        obj->peer_address,
                        NM_HASH_COMBINE_BOOLS(guint8,
                                              obj->use_ip4_broadcast_address,
                                              obj->a_acd_not_ready,
                                              obj->a_force_commit));
    nm_hash_update_strarr(h, obj->label);
}

int
nm_platform_ip4_address_cmp(const NMPlatformIP4Address *a,
                            const NMPlatformIP4Address *b,
                            NMPlatformIPAddressCmpType  cmp_type)
{
    NM_CMP_SELF(a, b);

    NM_CMP_FIELD(a, b, ifindex);
    NM_CMP_FIELD(a, b, plen);
    NM_CMP_FIELD(a, b, address);

    switch (cmp_type) {
    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID:
        /* for IPv4 addresses, you can add the same local address with differing peer-address
         * (IFA_ADDRESS), provided that their net-part differs. */
        NM_CMP_DIRECT_IP4_ADDR_SAME_PREFIX(a->peer_address, b->peer_address, a->plen);
        return 0;
    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY:
    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL:
        NM_CMP_FIELD(a, b, peer_address);
        NM_CMP_FIELD_STR(a, b, label);
        if (cmp_type == NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY) {
            NM_CMP_RETURN(_address_cmp_expiry((const NMPlatformIPAddress *) a,
                                              (const NMPlatformIPAddress *) b));

            /* Most flags are set by kernel. We only compare the ones that
             * NetworkManager actively sets.
             *
             * NM actively only sets IFA_F_NOPREFIXROUTE (and IFA_F_MANAGETEMPADDR for IPv6),
             * where nm_platform_ip_address_sync() sets IFA_F_NOPREFIXROUTE depending on
             * NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE.
             * There are thus no flags to compare for IPv4. */

            NM_CMP_DIRECT(nm_platform_ip4_broadcast_address_from_addr(a),
                          nm_platform_ip4_broadcast_address_from_addr(b));
        } else {
            NM_CMP_FIELD(a, b, timestamp);
            NM_CMP_FIELD(a, b, lifetime);
            NM_CMP_FIELD(a, b, preferred);
            NM_CMP_FIELD(a, b, n_ifa_flags);
            NM_CMP_FIELD(a, b, addr_source);
            NM_CMP_FIELD_UNSAFE(a, b, use_ip4_broadcast_address);
            if (a->use_ip4_broadcast_address)
                NM_CMP_FIELD(a, b, broadcast_address);
            NM_CMP_FIELD_UNSAFE(a, b, a_acd_not_ready);
            NM_CMP_FIELD_UNSAFE(a, b, a_force_commit);
        }
        return 0;
    }
    return nm_assert_unreachable_val(0);
}

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

void
nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState *h)
{
    nm_hash_update_vals(h,
                        obj->ifindex,
                        obj->addr_source,
                        obj->timestamp,
                        obj->lifetime,
                        obj->preferred,
                        obj->n_ifa_flags,
                        obj->plen,
                        obj->address,
                        obj->peer_address,
                        NM_HASH_COMBINE_BOOLS(guint8, obj->a_force_commit));
}

int
nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a,
                            const NMPlatformIP6Address *b,
                            NMPlatformIPAddressCmpType  cmp_type)
{
    const struct in6_addr *p_a, *p_b;

    NM_CMP_SELF(a, b);

    NM_CMP_FIELD(a, b, ifindex);
    NM_CMP_FIELD_IN6ADDR(a, b, address);

    switch (cmp_type) {
    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID:
        /* for IPv6 addresses, the prefix length is not part of the primary identifier. */
        return 0;
    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY:
    case NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL:
        NM_CMP_FIELD(a, b, plen);
        p_a = nm_platform_ip6_address_get_peer(a);
        p_b = nm_platform_ip6_address_get_peer(b);
        NM_CMP_DIRECT_MEMCMP(p_a, p_b, sizeof(*p_a));
        if (cmp_type == NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY) {
            NM_CMP_RETURN(_address_cmp_expiry((const NMPlatformIPAddress *) a,
                                              (const NMPlatformIPAddress *) b));

            /* Most flags are set by kernel. We only compare the ones that
             * NetworkManager actively sets.
             *
             * NM actively only sets IFA_F_NOPREFIXROUTE and IFA_F_MANAGETEMPADDR,
             * where nm_platform_ip_address_sync() sets IFA_F_NOPREFIXROUTE depending on
             * NMP_IP_ADDRESS_SYNC_FLAGS_WITH_NOPREFIXROUTE.
             * We thus only care about IFA_F_MANAGETEMPADDR. */
            NM_CMP_DIRECT(a->n_ifa_flags & IFA_F_MANAGETEMPADDR,
                          b->n_ifa_flags & IFA_F_MANAGETEMPADDR);
        } else {
            NM_CMP_FIELD(a, b, timestamp);
            NM_CMP_FIELD(a, b, lifetime);
            NM_CMP_FIELD(a, b, preferred);
            NM_CMP_FIELD(a, b, n_ifa_flags);
            NM_CMP_FIELD(a, b, addr_source);
            NM_CMP_FIELD_UNSAFE(a, b, a_force_commit);
        }
        return 0;
    }
    return nm_assert_unreachable_val(0);
}

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

static int
_address_pretty_sort_get_prio_4(in_addr_t addr)
{
    if (nm_ip4_addr_is_link_local(addr))
        return 0;
    return 1;
}

static int
_address_pretty_sort_get_prio_6(const struct in6_addr *addr)
{
    if (IN6_IS_ADDR_V4MAPPED(addr))
        return 0;
    if (IN6_IS_ADDR_V4COMPAT(addr))
        return 1;
    if (IN6_IS_ADDR_UNSPECIFIED(addr))
        return 2;
    if (IN6_IS_ADDR_LOOPBACK(addr))
        return 3;
    if (IN6_IS_ADDR_LINKLOCAL(addr))
        return 4;
    if (IN6_IS_ADDR_SITELOCAL(addr))
        return 5;
    return 6;
}

int
nm_platform_ip4_address_pretty_sort_cmp(const NMPlatformIP4Address *a1,
                                        const NMPlatformIP4Address *a2)
{
    in_addr_t n1;
    in_addr_t n2;

    nm_assert(a1);
    nm_assert(a2);

    /* Sort by address type. For example link local will
     * be sorted *after* a global address. */
    NM_CMP_DIRECT(_address_pretty_sort_get_prio_4(a2->address),
                  _address_pretty_sort_get_prio_4(a1->address));

    /* Sort the addresses based on their source. */
    NM_CMP_DIRECT(a2->addr_source, a1->addr_source);

    NM_CMP_DIRECT((a2->label[0] == '\0'), (a1->label[0] == '\0'));

    /* Finally, sort addresses lexically. We compare only the
     * network part so that the order of addresses in the same
     * subnet (and thus also the primary/secondary role) is
     * preserved.
     */
    n1 = nm_ip4_addr_clear_host_address(a1->address, a1->plen);
    n2 = nm_ip4_addr_clear_host_address(a2->address, a2->plen);
    NM_CMP_DIRECT_MEMCMP(&n1, &n2, sizeof(guint32));
    return 0;
}

int
nm_platform_ip6_address_pretty_sort_cmp(const NMPlatformIP6Address *a1,
                                        const NMPlatformIP6Address *a2,
                                        gboolean                    prefer_temp)
{
    gboolean ipv6_privacy1;
    gboolean ipv6_privacy2;

    nm_assert(a1);
    nm_assert(a2);

    /* tentative addresses are always sorted back... */
    /* sort tentative addresses after non-tentative. */
    NM_CMP_DIRECT(NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_TENTATIVE),
                  NM_FLAGS_HAS(a2->n_ifa_flags, IFA_F_TENTATIVE));

    /* Sort by address type. For example link local will
     * be sorted *after* site local or global. */
    NM_CMP_DIRECT(_address_pretty_sort_get_prio_6(&a2->address),
                  _address_pretty_sort_get_prio_6(&a1->address));

    ipv6_privacy1 = NM_FLAGS_ANY(a1->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_SECONDARY);
    ipv6_privacy2 = NM_FLAGS_ANY(a2->n_ifa_flags, IFA_F_MANAGETEMPADDR | IFA_F_SECONDARY);
    if (ipv6_privacy1 || ipv6_privacy2) {
        gboolean public1 = TRUE;
        gboolean public2 = TRUE;

        if (ipv6_privacy1) {
            if (a1->n_ifa_flags & IFA_F_SECONDARY)
                public1 = prefer_temp;
            else
                public1 = !prefer_temp;
        }
        if (ipv6_privacy2) {
            if (a2->n_ifa_flags & IFA_F_SECONDARY)
                public2 = prefer_temp;
            else
                public2 = !prefer_temp;
        }

        NM_CMP_DIRECT(public2, public1);
    }

    /* Sort the addresses based on their source. */
    NM_CMP_DIRECT(a2->addr_source, a1->addr_source);

    /* sort permanent addresses before non-permanent. */
    NM_CMP_DIRECT(NM_FLAGS_HAS(a2->n_ifa_flags, IFA_F_PERMANENT),
                  NM_FLAGS_HAS(a1->n_ifa_flags, IFA_F_PERMANENT));

    /* finally sort addresses lexically */
    NM_CMP_DIRECT_IN6ADDR(&a1->address, &a2->address);
    NM_CMP_DIRECT_MEMCMP(a1, a2, sizeof(*a1));
    return 0;
}

void
nm_platform_ip4_address_set_addr(NMPlatformIP4Address *addr, in_addr_t address, guint8 plen)
{
    nm_assert(plen <= 32);

    addr->address      = address;
    addr->peer_address = address;
    addr->plen         = plen;
}

const struct in6_addr *
nm_platform_ip6_address_get_peer(const NMPlatformIP6Address *addr)
{
    if (IN6_IS_ADDR_UNSPECIFIED(&addr->peer_address)
        || IN6_ARE_ADDR_EQUAL(&addr->peer_address, &addr->address))
        return &addr->address;
    return &addr->peer_address;
}

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

/**
 * nm_platform_ip4_address_to_string:
 * @route: pointer to NMPlatformIP4Address address structure
 * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
 * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
 *
 * A method for converting an address struct into a string representation.
 *
 * Example output: ""
 *
 * Returns: a string representation of the address.
 */
const char *
nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf, gsize len)
{
    char        s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE];
    char        s_address[INET_ADDRSTRLEN];
    char        s_peer[INET_ADDRSTRLEN];
    char        str_dev[30];
    char        str_label[32];
    char        str_lft[30];
    char        str_pref[30];
    char        str_time[50];
    char        s_source[50];
    char       *str_peer = NULL;
    const char *str_lft_p, *str_pref_p, *str_time_p;
    gint32      now = nm_utils_get_monotonic_timestamp_sec();
    in_addr_t   broadcast_address;
    char        str_broadcast[INET_ADDRSTRLEN];

    if (!nm_utils_to_string_buffer_init_null(address, &buf, &len))
        return buf;

    inet_ntop(AF_INET, &address->address, s_address, sizeof(s_address));

    if (address->peer_address != address->address) {
        inet_ntop(AF_INET, &address->peer_address, s_peer, sizeof(s_peer));
        str_peer = g_strconcat(" ptp ", s_peer, NULL);
    }

    if (*address->label)
        g_snprintf(str_label, sizeof(str_label), " label %s", address->label);
    else
        str_label[0] = 0;

    str_lft_p = _lifetime_to_string(address->timestamp,
                                    address->lifetime ?: NM_PLATFORM_LIFETIME_PERMANENT,
                                    now,
                                    str_lft,
                                    sizeof(str_lft)),
    str_pref_p =
        (address->lifetime == address->preferred)
            ? str_lft_p
            : (_lifetime_to_string(address->timestamp,
                                   address->lifetime ? MIN(address->preferred, address->lifetime)
                                                     : NM_PLATFORM_LIFETIME_PERMANENT,
                                   now,
                                   str_pref,
                                   sizeof(str_pref)));
    str_time_p = _lifetime_summary_to_string(now,
                                             address->timestamp,
                                             address->preferred,
                                             address->lifetime,
                                             str_time,
                                             sizeof(str_time));

    broadcast_address = nm_platform_ip4_broadcast_address_from_addr(address);

    g_snprintf(
        buf,
        len,
        "%s/%d"
        "%s%s" /* broadcast */
        " lft %s"
        " pref %s"
        "%s" /* time */
        "%s" /* peer  */
        "%s" /* dev */
        "%s" /* flags */
        "%s" /* label */
        " src %s"
        "%s" /* a_acd_not_ready */
        "%s" /* a_force_commit */
        "",
        s_address,
        address->plen,
        broadcast_address != 0u || address->use_ip4_broadcast_address
            ? (address->use_ip4_broadcast_address ? " brd " : " brd* ")
            : "",
        broadcast_address != 0u || address->use_ip4_broadcast_address
            ? nm_inet4_ntop(broadcast_address, str_broadcast)
            : "",
        str_lft_p,
        str_pref_p,
        str_time_p,
        str_peer ?: "",
        _to_string_dev(str_dev, address->ifindex),
        _to_string_ifa_flags(address->n_ifa_flags, s_flags, sizeof(s_flags)),
        str_label,
        nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)),
        address->a_acd_not_ready ? " ip4acd-not-ready" : "",
        address->a_force_commit ? " force-commit" : "");
    g_free(str_peer);
    return buf;
}

/**
 * nm_platform_ip6_address_to_string:
 * @route: pointer to NMPlatformIP6Address address structure
 * @buf: (allow-none): an optional buffer. If %NULL, a static buffer is used.
 * @len: the size of the @buf. If @buf is %NULL, this argument is ignored.
 *
 * A method for converting an address struct into a string representation.
 *
 * Example output: "2001:db8:0:f101::1/64 lft 4294967295 pref 4294967295 time 16922666 on dev em1"
 *
 * Returns: a string representation of the address.
 */
const char *
nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf, gsize len)
{
    char        s_flags[TO_STRING_IFA_FLAGS_BUF_SIZE];
    char        s_address[INET6_ADDRSTRLEN];
    char        s_peer[INET6_ADDRSTRLEN];
    char        str_lft[30];
    char        str_pref[30];
    char        str_time[50];
    char        s_source[50];
    char        str_dev[30];
    char       *str_peer = NULL;
    const char *str_lft_p, *str_pref_p, *str_time_p;
    gint32      now = nm_utils_get_monotonic_timestamp_sec();

    if (!nm_utils_to_string_buffer_init_null(address, &buf, &len))
        return buf;

    inet_ntop(AF_INET6, &address->address, s_address, sizeof(s_address));

    if (!IN6_IS_ADDR_UNSPECIFIED(&address->peer_address)) {
        inet_ntop(AF_INET6, &address->peer_address, s_peer, sizeof(s_peer));
        str_peer = g_strconcat(" ptp ", s_peer, NULL);
    }

    str_lft_p = _lifetime_to_string(address->timestamp,
                                    address->lifetime ?: NM_PLATFORM_LIFETIME_PERMANENT,
                                    now,
                                    str_lft,
                                    sizeof(str_lft)),
    str_pref_p =
        (address->lifetime == address->preferred)
            ? str_lft_p
            : (_lifetime_to_string(address->timestamp,
                                   address->lifetime ? MIN(address->preferred, address->lifetime)
                                                     : NM_PLATFORM_LIFETIME_PERMANENT,
                                   now,
                                   str_pref,
                                   sizeof(str_pref)));
    str_time_p = _lifetime_summary_to_string(now,
                                             address->timestamp,
                                             address->preferred,
                                             address->lifetime,
                                             str_time,
                                             sizeof(str_time));

    g_snprintf(
        buf,
        len,
        "%s/%d lft %s pref %s%s%s%s%s src %s"
        "%s" /* a_force_commit */
        "",
        s_address,
        address->plen,
        str_lft_p,
        str_pref_p,
        str_time_p,
        str_peer ?: "",
        _to_string_dev(str_dev, address->ifindex),
        _to_string_ifa_flags(address->n_ifa_flags, s_flags, sizeof(s_flags)),
        nmp_utils_ip_config_source_to_string(address->addr_source, s_source, sizeof(s_source)),
        address->a_force_commit ? " force-commit" : "");
    g_free(str_peer);
    return buf;
}

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

gboolean
nm_platform_ip_address_match(int                        addr_family,
                             const NMPlatformIPAddress *address,
                             NMPlatformMatchFlags       match_flag)
{
    nm_assert(!NM_FLAGS_ANY(
        match_flag,
        ~(NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY | NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY)));
    nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY));
    nm_assert(NM_FLAGS_ANY(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY));

    if (addr_family == AF_INET) {
        if (nm_ip4_addr_is_link_local(((NMPlatformIP4Address *) address)->address)) {
            if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL))
                return FALSE;
        } else {
            if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL))
                return FALSE;
        }
    } else {
        if (IN6_IS_ADDR_LINKLOCAL(address->address_ptr)) {
            if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL))
                return FALSE;
        } else {
            if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL))
                return FALSE;
        }
    }

    if (NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_DADFAILED)) {
        if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED))
            return FALSE;
    } else if (NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_TENTATIVE)
               && !NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_OPTIMISTIC)) {
        if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE))
            return FALSE;
    } else if (NM_FLAGS_HAS(address->n_ifa_flags, IFA_F_DEPRECATED)) {
        if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_DEPRECATED))
            return FALSE;
    } else {
        if (!NM_FLAGS_HAS(match_flag, NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
            return FALSE;
    }

    return TRUE;
}

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

const _NMPlatformVTableAddressUnion nm_platform_vtable_address = {
    .v4 =
        {
            .is_ip4         = TRUE,
            .obj_type       = NMP_OBJECT_TYPE_IP4_ADDRESS,
            .addr_family    = AF_INET,
            .sizeof_address = sizeof(NMPlatformIP4Address),
            .address_cmp =
                (int (*)(const NMPlatformIPXAddress *a,
                         const NMPlatformIPXAddress *b,
                         NMPlatformIPAddressCmpType  cmp_type)) nm_platform_ip4_address_cmp,
            .address_to_string = (const char *(*) (const NMPlatformIPXAddress *address,
                                                   char                       *buf,
                                                   gsize len)) nm_platform_ip4_address_to_string,
        },
    .v6 =
        {
            .is_ip4         = FALSE,
            .obj_type       = NMP_OBJECT_TYPE_IP6_ADDRESS,
            .addr_family    = AF_INET6,
            .sizeof_address = sizeof(NMPlatformIP6Address),
            .address_cmp =
                (int (*)(const NMPlatformIPXAddress *a,
                         const NMPlatformIPXAddress *b,
                         NMPlatformIPAddressCmpType  cmp_type)) nm_platform_ip6_address_cmp,
            .address_to_string = (const char *(*) (const NMPlatformIPXAddress *address,
                                                   char                       *buf,
                                                   gsize len)) nm_platform_ip6_address_to_string,
        },
};
