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

#ifndef __NMP_PLOBJ_H__
#define __NMP_PLOBJ_H__

#include "libnm-base/nm-base.h"

#include "nmp-base.h"

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

/* This is used with _nm_align() on the platform structs. Note that
 * "align" attribute can only increase the natural alignment, unless
 * also "packed" is specified. That's what we want.
 * https://gcc.gnu.org/onlinedocs/gcc/Common-Type-Attributes.html#Common-Type-Attributes.
 */
#define _NMPlatformObject_Align (NM_MAX_CONST(_nm_alignof(void *), _nm_alignof(gint64)))

struct _NMPlatformObject {
    /* the object type has no fields of its own, it is only used to having
     * a special pointer type that can be used to indicate "any" type. */
    char _dummy_don_t_use_me;
} _nm_align(_NMPlatformObject_Align);

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

#define __NMPlatformObjWithIfindex_COMMON \
    int ifindex;                          \
    ;

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

#define __NMPlatformIPAddress_COMMON                                                         \
    __NMPlatformObjWithIfindex_COMMON;                                                       \
    NMIPConfigSource addr_source;                                                            \
                                                                                             \
    /* Timestamp in seconds in the reference system of nm_utils_get_monotonic_timestamp_*().
     *
     * The rules are:
     * 1 @lifetime==0: @timestamp and @preferred is irrelevant (but mostly set to 0 too). Such addresses
     *   are permanent. This rule is so that unset addresses (calloc) are permanent by default.
     * 2 @lifetime==@preferred==NM_PLATFORM_LIFETIME_PERMANENT: @timestamp is irrelevant (but mostly
     *   set to 0). Such addresses are permanent.
     * 3 Non permanent addresses should (almost) always have @timestamp > 0. 0 is not a valid timestamp
     *   and never returned by nm_utils_get_monotonic_timestamp_sec(). In this case @valid/@preferred
     *   is anchored at @timestamp.
     * 4 Non permanent addresses with @timestamp == 0 are implicitly anchored at *now*, thus the time
     *   moves as time goes by. This is usually not useful, except e.g. nm_platform_ip[46]_address_add().
     *
     * Non permanent addresses from DHCP/RA might have the @timestamp set to the moment of when the
     * lease was received. Addresses from kernel might have the @timestamp based on the last modification
     * time of the addresses. But don't rely on this behaviour, the @timestamp is only defined for anchoring
     * @lifetime and @preferred.
     */ \
    guint32 timestamp;                                                                       \
    guint32 lifetime;  /* seconds since timestamp */                                         \
    guint32 preferred; /* seconds since timestamp */                                         \
                                                                                             \
    /* ifa_flags in 'struct ifaddrmsg' from <linux/if_addr.h>, extended to 32 bit by
     * IFA_FLAGS attribute. */         \
    guint32 n_ifa_flags;                                                                     \
                                                                                             \
    bool use_ip4_broadcast_address : 1;                                                      \
                                                                                             \
    /* Meta flags not honored by NMPlatform (netlink code). Instead, they can be
     * used by the upper layers which use NMPlatformIPRoute to track addresses that
     * should be configured. */             \
    bool a_force_commit : 1;                                                                 \
                                                                                             \
    /* Don't have a bitfield as last field in __NMPlatformIPAddress_COMMON. It would then
     * be unclear how the following fields get merged. We could also use a zero bitfield,
     * but instead we just have there the uint8 field. */    \
    guint8 plen;                                                                             \
    ;

/**
 * NMPlatformIPAddress:
 *
 * Common parts of NMPlatformIP4Address and NMPlatformIP6Address.
 **/
typedef struct {
    __NMPlatformIPAddress_COMMON;
    _nm_alignas(NMIPAddr) guint8 address_ptr[];
} _nm_alignas(NMPlatformObject) NMPlatformIPAddress;

/**
 * NMPlatformIP4Address:
 * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec()
 **/
struct _NMPlatformIP4Address {
    __NMPlatformIPAddress_COMMON;

    /* The local address IFA_LOCAL. */
    _nm_alignas(NMIPAddr) in_addr_t address;

    /* The IFA_ADDRESS PTP peer address. This field is rather important, because
     * it constitutes the identifier for the IPv4 address (e.g. you can add two
     * addresses that only differ by their peer's network-part.
     *
     * Beware that for most cases, NetworkManager doesn't want to set an explicit
     * peer-address. However, that corresponds to setting the peer address to @address
     * itself. Leaving peer-address unset/zero, means explicitly setting the peer
     * address to 0.0.0.0, which you probably don't want.
     * */
    in_addr_t peer_address; /* PTP peer address */

    /* IFA_BROADCAST.
     *
     * This parameter is ignored unless use_ip4_broadcast_address is TRUE.
     * See nm_platform_ip4_broadcast_address_from_addr(). */
    in_addr_t broadcast_address;

    char label[NM_IFNAMSIZ];

    /* Whether the address is ready to be configured. By default, an address is, but this
     * flag may indicate that the address is just for tracking purpose only, but the ACD
     * state is not yet ready for the address to be configured. */
    bool a_acd_not_ready : 1;
} _nm_alignas(NMPlatformObject);

/**
 * NMPlatformIP6Address:
 * @timestamp: timestamp as returned by nm_utils_get_monotonic_timestamp_sec()
 **/
struct _NMPlatformIP6Address {
    __NMPlatformIPAddress_COMMON;
    _nm_alignas(NMIPAddr) struct in6_addr address;
    struct in6_addr peer_address;
} _nm_alignas(NMPlatformObject);

typedef union {
    NMPlatformIPAddress  ax;
    NMPlatformIP4Address a4;
    NMPlatformIP6Address a6;
} NMPlatformIPXAddress;

#undef __NMPlatformIPAddress_COMMON

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

typedef enum {
    NM_PLATFORM_IP_ADDRESS_CMP_TYPE_ID,

    NM_PLATFORM_IP_ADDRESS_CMP_TYPE_SEMANTICALLY,

    NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL,
} NMPlatformIPAddressCmpType;

#define NM_PLATFORM_IP_ADDRESS_CAST(address) \
    NM_CONSTCAST(NMPlatformIPAddress,        \
                 (address),                  \
                 NMPlatformIPXAddress,       \
                 NMPlatformIP4Address,       \
                 NMPlatformIP6Address)

#define NM_PLATFORM_IP4_ADDRESS_INIT(...) (&((const NMPlatformIP4Address) {__VA_ARGS__}))

#define NM_PLATFORM_IP6_ADDRESS_INIT(...) (&((const NMPlatformIP6Address) {__VA_ARGS__}))

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

typedef struct {
    bool          is_ip4;
    NMPObjectType obj_type;
    gint8         addr_family;
    guint8        sizeof_address;
    int (*address_cmp)(const NMPlatformIPXAddress *a,
                       const NMPlatformIPXAddress *b,
                       NMPlatformIPAddressCmpType  cmp_type);
    const char *(*address_to_string)(const NMPlatformIPXAddress *address, char *buf, gsize len);
} NMPlatformVTableAddress;

typedef union {
    struct {
        NMPlatformVTableAddress v6;
        NMPlatformVTableAddress v4;
    };
    NMPlatformVTableAddress vx[2];
} _NMPlatformVTableAddressUnion;

extern const _NMPlatformVTableAddressUnion nm_platform_vtable_address;

void nm_platform_ip4_address_hash_update(const NMPlatformIP4Address *obj, NMHashState *h);

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

static inline int
nm_platform_ip4_address_cmp_full(const NMPlatformIP4Address *a, const NMPlatformIP4Address *b)
{
    return nm_platform_ip4_address_cmp(a, b, NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL);
}

static inline gboolean
nm_platform_ip4_address_is_link_local(const NMPlatformIP4Address *a)
{
    nm_assert(a);

    return nm_ip4_addr_is_link_local(a->address) && a->plen == NM_IPV4LL_PREFIXLEN
           && a->address == a->peer_address;
}

void nm_platform_ip6_address_hash_update(const NMPlatformIP6Address *obj, NMHashState *h);

int nm_platform_ip6_address_cmp(const NMPlatformIP6Address *a,
                                const NMPlatformIP6Address *b,
                                NMPlatformIPAddressCmpType  cmp_type);

static inline int
nm_platform_ip6_address_cmp_full(const NMPlatformIP6Address *a, const NMPlatformIP6Address *b)
{
    return nm_platform_ip6_address_cmp(a, b, NM_PLATFORM_IP_ADDRESS_CMP_TYPE_FULL);
}

int nm_platform_ip4_address_pretty_sort_cmp(const NMPlatformIP4Address *a1,
                                            const NMPlatformIP4Address *a2);

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

static inline in_addr_t
nm_platform_ip4_broadcast_address_from_addr(const NMPlatformIP4Address *addr)
{
    nm_assert(addr);

    if (addr->use_ip4_broadcast_address)
        return addr->broadcast_address;

    /* the set broadcast-address gets ignored, and we determine a default brd base
     * on the peer IFA_ADDRESS. */
    if (addr->peer_address != 0u && addr->plen < 31 /* RFC3021 */)
        return nm_ip4_addr_get_broadcast_address(addr->peer_address, addr->plen);
    return 0u;
}

void nm_platform_ip4_address_set_addr(NMPlatformIP4Address *addr, in_addr_t address, guint8 plen);

const struct in6_addr *nm_platform_ip6_address_get_peer(const NMPlatformIP6Address *addr);

static inline gpointer
nm_platform_ip_address_get_peer_address(int addr_family, const NMPlatformIPAddress *addr)
{
    nm_assert_addr_family(addr_family);
    nm_assert(addr);

    if (NM_IS_IPv4(addr_family))
        return &((NMPlatformIP4Address *) addr)->peer_address;
    return &((NMPlatformIP6Address *) addr)->peer_address;
}

const char *
nm_platform_ip4_address_to_string(const NMPlatformIP4Address *address, char *buf, gsize len);
const char *
nm_platform_ip6_address_to_string(const NMPlatformIP6Address *address, char *buf, gsize len);

int nm_platform_ip_address_cmp_expiry(const NMPlatformIPAddress *a, const NMPlatformIPAddress *b);

NMPlatformIP4Route *nm_platform_ip4_address_generate_device_route(const NMPlatformIP4Address *addr,
                                                                  int                 ifindex,
                                                                  guint32             route_table,
                                                                  guint32             route_metric,
                                                                  NMPlatformIP4Route *dst);

typedef enum {

    /* match-flags are strictly inclusive. That means,
     * by default nothing is matched, but if you enable a particular
     * flag, a candidate that matches passes the check.
     *
     * In other words: adding more flags can only extend the result
     * set of matching objects.
     *
     * Also, the flags form partitions. Like, an address can be either of
     * ADDRTYPE_NORMAL or ADDRTYPE_LINKLOCAL, but never both. Same for
     * the ADDRSTATE match types.
     */
    NM_PLATFORM_MATCH_WITH_NONE = 0,

    NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL    = (1LL << 0),
    NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL = (1LL << 1),
    NM_PLATFORM_MATCH_WITH_ADDRTYPE__ANY =
        NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL | NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL,

    NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL     = (1LL << 2),
    NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE  = (1LL << 3),
    NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED  = (1LL << 4),
    NM_PLATFORM_MATCH_WITH_ADDRSTATE_DEPRECATED = (1LL << 5),
    NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY =
        NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL | NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE
        | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DADFAILED | NM_PLATFORM_MATCH_WITH_ADDRSTATE_DEPRECATED,
} NMPlatformMatchFlags;

gboolean nm_platform_ip_address_match(int                        addr_family,
                                      const NMPlatformIPAddress *addr,
                                      NMPlatformMatchFlags       match_flag);

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

#endif /* __NMP_PLOBJ_H__ */
