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

#ifndef __NM_NETLINK_H__
#define __NM_NETLINK_H__

#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/genetlink.h>

#include "libnm-std-aux/unaligned.h"

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

#define NLMSGERR_ATTR_UNUSED 0
#define NLMSGERR_ATTR_MSG    1
#define NLMSGERR_ATTR_OFFS   2
#define NLMSGERR_ATTR_COOKIE 3
#define NLMSGERR_ATTR_MAX    3

#ifndef NLM_F_ACK_TLVS
#define NLM_F_ACK_TLVS 0x200
#endif

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

/* Basic attribute data types */
enum {
    NLA_UNSPEC, /* Unspecified type, binary data chunk */
    NLA_U8,     /* 8 bit integer */
    NLA_U16,    /* 16 bit integer */
    NLA_S32,    /* 32 bit integer */
    NLA_U32,    /* 32 bit integer */
    NLA_U64,    /* 64 bit integer */
    NLA_STRING, /* NUL terminated character string */
    NLA_FLAG,   /* Flag */
    NLA_NESTED, /* Nested attributes */
    __NLA_TYPE_MAX,
};

#define NLA_TYPE_MAX (__NLA_TYPE_MAX - 1)

struct nl_msg;

/* This is similar to "struct nl_msg", in that it contains a
 * netlink message including additional information like the
 * src, creds, protocol.
 *
 * The difference is that "struct nl_msg" is an opaque type and
 * contains a copy of the message (requiring two heap allocations).
 * "struct nl_msg_lite" can be on the stack and it can directly
 * point to the receive buffer, without need to copy the message.
 * That can be useful, if you don't need to clone the message and
 * just need to pass it "down the stack" for somebody to parse
 * the message. */
struct nl_msg_lite {
    int                       nm_protocol;
    const struct sockaddr_nl *nm_src;
    const struct sockaddr_nl *nm_dst;
    const struct ucred       *nm_creds;
    const struct nlmsghdr    *nm_nlh;
    uint32_t                  nm_size;
};

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

const char *nl_nlmsgtype2str(int type, char *buf, size_t size);

const char *nl_nlmsg_flags2str(int flags, char *buf, size_t len);

const char *nl_nlmsghdr_to_str(int                    netlink_protocol,
                               guint32                pktinfo_group,
                               const struct nlmsghdr *hdr,
                               char                  *buf,
                               gsize                  len);

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

struct nla_policy {
    /* Type of attribute or NLA_UNSPEC */
    uint8_t type;

    /* Minimal length of payload required */
    uint8_t minlen;

    /* Maximal length of payload allowed */
    uint16_t maxlen;
};

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

/* static asserts that @tb and @policy are suitable arguments to nla_parse(). */
#if _NM_CC_SUPPORT_GENERIC
#define _nl_static_assert_tb(tb, policy)                                                   \
    G_STMT_START                                                                           \
    {                                                                                      \
        G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(tb) > 0);                                        \
                                                                                           \
        /* We allow @policy to be either a C array or NULL. The sizeof()
         * must either match the expected array size or we check that
         * "policy" has typeof(NULL). This isn't a perfect compile time check,
         * but good enough. */                   \
        G_STATIC_ASSERT_EXPR(                                                              \
            _Generic((policy), typeof(NULL) : 1, default                                   \
                     : (sizeof(policy) == G_N_ELEMENTS(tb) * sizeof(struct nla_policy)))); \
    }                                                                                      \
    G_STMT_END
#else
#define _nl_static_assert_tb(tb, policy) G_STATIC_ASSERT_EXPR(G_N_ELEMENTS(tb) > 0)
#endif

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

static inline int
nla_attr_size(int payload)
{
    nm_assert(payload >= 0);

    return NLA_HDRLEN + payload;
}

static inline int
nla_total_size(int payload)
{
    return NLA_ALIGN(nla_attr_size(payload));
}

static inline int
nla_padlen(int payload)
{
    return nla_total_size(payload) - nla_attr_size(payload);
}

struct nlattr *nla_reserve(struct nl_msg *msg, int attrtype, int attrlen);

static inline uint16_t
nla_len(const struct nlattr *nla)
{
    nm_assert(nla);
    nm_assert(nla->nla_len >= NLA_HDRLEN);

    return nla->nla_len - ((uint16_t) NLA_HDRLEN);
}

static inline int
nla_type(const struct nlattr *nla)
{
    return nla->nla_type & NLA_TYPE_MASK;
}

static inline void *
nla_data(const struct nlattr *nla)
{
    return &(((char *) nla)[NLA_HDRLEN]);
}

#define nla_data_as(type, nla)                                          \
    ({                                                                  \
        const struct nlattr *_nla = (nla);                              \
                                                                        \
        nm_assert(nla_len(_nla) >= sizeof(type));                       \
                                                                        \
        /* note that casting the pointer is undefined behavior in C, if
         * the data has wrong alignment. Netlink data is aligned to 4 bytes,
         * that means, if the alignment is larger than 4, this is invalid. */ \
        G_STATIC_ASSERT_EXPR(_nm_alignof(type) <= NLA_ALIGNTO);         \
                                                                        \
        (type *) nla_data(_nla);                                        \
    })

static inline uint8_t
nla_get_u8(const struct nlattr *nla)
{
    nm_assert(nla_len(nla) >= sizeof(uint8_t));

    return *((const uint8_t *) nla_data(nla));
}

static inline int8_t
nla_get_s8(const struct nlattr *nla)
{
    nm_assert(nla_len(nla) >= sizeof(int8_t));

    return *((const int8_t *) nla_data(nla));
}

static inline uint8_t
nla_get_u8_cond(/*const*/ struct nlattr *const *tb, int attr, uint8_t default_val)
{
    nm_assert(tb);
    nm_assert(attr >= 0);

    return tb[attr] ? nla_get_u8(tb[attr]) : default_val;
}

static inline uint16_t
nla_get_u16(const struct nlattr *nla)
{
    nm_assert(nla_len(nla) >= sizeof(uint16_t));

    return *((const uint16_t *) nla_data(nla));
}

static inline uint32_t
nla_get_u32(const struct nlattr *nla)
{
    nm_assert(nla_len(nla) >= sizeof(uint32_t));

    return *((const uint32_t *) nla_data(nla));
}

static inline int32_t
nla_get_s32(const struct nlattr *nla)
{
    nm_assert(nla_len(nla) >= sizeof(int32_t));

    return *((const int32_t *) nla_data(nla));
}

static inline uint64_t
nla_get_u64(const struct nlattr *nla)
{
    nm_assert(nla_len(nla) >= sizeof(uint64_t));

    return unaligned_read_ne64(nla_data(nla));
}

static inline uint64_t
nla_get_be64(const struct nlattr *nla)
{
    nm_assert(nla_len(nla) >= sizeof(uint64_t));

    return unaligned_read_be64(nla_data(nla));
}

static inline char *
nla_get_string(const struct nlattr *nla)
{
    return nla_data(nla);
}

size_t nla_strlcpy(char *dst, const struct nlattr *nla, size_t dstsize);

size_t nla_memcpy(void *dst, const struct nlattr *nla, size_t dstsize);

#define nla_memcpy_checked_size(dst, nla, dstsize)                       \
    G_STMT_START                                                         \
    {                                                                    \
        void *const                _dst     = (dst);                     \
        const struct nlattr *const _nla     = (nla);                     \
        const size_t               _dstsize = (dstsize);                 \
        size_t                     _srcsize;                             \
                                                                         \
        /* assert that, if @nla is given, that it has the exact expected
         * size. This implies that the caller previously verified the length
         * of the attribute (via minlen/maxlen at nla_parse()). */ \
                                                                         \
        if (_nla) {                                                      \
            _srcsize = nla_memcpy(_dst, _nla, _dstsize);                 \
            nm_assert(_srcsize == _dstsize);                             \
        }                                                                \
    }                                                                    \
    G_STMT_END

int nla_put(struct nl_msg *msg, int attrtype, int datalen, const void *data);

static inline int
nla_put_string(struct nl_msg *msg, int attrtype, const char *str)
{
    nm_assert(str);

    return nla_put(msg, attrtype, strlen(str) + 1, str);
}

static inline int
nla_put_uint8(struct nl_msg *msg, int attrtype, uint8_t val)
{
    return nla_put(msg, attrtype, sizeof(val), &val);
}

static inline int
nla_put_uint16(struct nl_msg *msg, int attrtype, uint16_t val)
{
    return nla_put(msg, attrtype, sizeof(val), &val);
}

static inline int
nla_put_uint32(struct nl_msg *msg, int attrtype, uint32_t val)
{
    return nla_put(msg, attrtype, sizeof(val), &val);
}

#define NLA_PUT(msg, attrtype, attrlen, data)                  \
    G_STMT_START                                               \
    {                                                          \
        if (nla_put((msg), (attrtype), (attrlen), (data)) < 0) \
            goto nla_put_failure;                              \
    }                                                          \
    G_STMT_END

#define NLA_PUT_TYPE(msg, type, attrtype, value)                 \
    G_STMT_START                                                 \
    {                                                            \
        type const _nla_tmp = value;                             \
                                                                 \
        NLA_PUT((msg), (attrtype), sizeof(_nla_tmp), &_nla_tmp); \
    }                                                            \
    G_STMT_END

#define NLA_PUT_U8(msg, attrtype, value) NLA_PUT_TYPE(msg, uint8_t, attrtype, value)

#define NLA_PUT_S8(msg, attrtype, value) NLA_PUT_TYPE(msg, int8_t, attrtype, value)

#define NLA_PUT_U16(msg, attrtype, value) NLA_PUT_TYPE(msg, uint16_t, attrtype, value)

#define NLA_PUT_U32(msg, attrtype, value) NLA_PUT_TYPE(msg, uint32_t, attrtype, value)

#define NLA_PUT_S32(msg, attrtype, value) NLA_PUT_TYPE(msg, int32_t, attrtype, value)

#define NLA_PUT_U64(msg, attrtype, value) NLA_PUT_TYPE(msg, uint64_t, attrtype, value)

#define NLA_PUT_STRING(msg, attrtype, value) NLA_PUT(msg, attrtype, (int) strlen(value) + 1, value)

#define NLA_PUT_FLAG(msg, attrtype) NLA_PUT(msg, attrtype, 0, NULL)

struct nlattr *nla_find(const struct nlattr *head, int len, int attrtype);

static inline int
nla_ok(const struct nlattr *nla, int remaining)
{
    return remaining >= (int) sizeof(*nla) && nla->nla_len >= sizeof(*nla)
           && nla->nla_len <= remaining;
}

static inline struct nlattr *
nla_next(const struct nlattr *nla, int *remaining)
{
    int totlen = NLA_ALIGN(nla->nla_len);

    *remaining -= totlen;
    return NM_CAST_ALIGN(struct nlattr, (((char *) nla) + totlen));
}

#define nla_for_each_attr(pos, head, len, rem) \
    for (pos = head, rem = len; nla_ok(pos, rem); pos = nla_next(pos, &(rem)))

#define nla_for_each_nested(pos, nla, rem)                                            \
    for (pos = (struct nlattr *) nla_data(nla), rem = nla_len(nla); nla_ok(pos, rem); \
         pos = nla_next(pos, &(rem)))

void           nla_nest_cancel(struct nl_msg *msg, const struct nlattr *attr);
struct nlattr *nla_nest_start(struct nl_msg *msg, int attrtype);
int            nla_nest_end(struct nl_msg *msg, struct nlattr *start);

#define NLA_NEST_END(msg, nest_start)              \
    G_STMT_START                                   \
    {                                              \
        if (nla_nest_end((msg), (nest_start)) < 0) \
            goto nla_put_failure;                  \
    }                                              \
    G_STMT_END

int nla_parse(struct nlattr           *tb[],
              int                      maxtype,
              struct nlattr           *head,
              int                      len,
              const struct nla_policy *policy);

#define nla_parse_arr(tb, head, len, policy)                            \
    ({                                                                  \
        _nl_static_assert_tb((tb), (policy));                           \
                                                                        \
        nla_parse((tb), G_N_ELEMENTS(tb) - 1, (head), (len), (policy)); \
    })

static inline int
nla_parse_nested(struct nlattr           *tb[],
                 int                      maxtype,
                 struct nlattr           *nla,
                 const struct nla_policy *policy)
{
    return nla_parse(tb, maxtype, nla_data(nla), nla_len(nla), policy);
}

#define nla_parse_nested_arr(tb, nla, policy)                          \
    ({                                                                 \
        _nl_static_assert_tb((tb), (policy));                          \
                                                                       \
        nla_parse_nested((tb), G_N_ELEMENTS(tb) - 1, (nla), (policy)); \
    })

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

struct nl_msg *nlmsg_alloc(size_t len);

struct nl_msg *nlmsg_alloc_convert(struct nlmsghdr *hdr);

struct nl_msg *nlmsg_alloc_new(size_t size, uint16_t nlmsgtype, uint16_t flags);

void *nlmsg_reserve(struct nl_msg *n, uint32_t len, uint32_t pad);

int nlmsg_append(struct nl_msg *n, const void *data, uint32_t len, uint32_t pad);

#define nlmsg_append_struct(n, data) (nlmsg_append((n), (data), sizeof(*(data)), NLMSG_ALIGNTO))

void nlmsg_free(struct nl_msg *msg);

static inline int
nlmsg_size(int payload)
{
    nm_assert(payload >= 0 && payload < G_MAXINT - NLMSG_HDRLEN - 4);
    return NLMSG_HDRLEN + payload;
}

static inline int
nlmsg_total_size(int payload)
{
    return NLMSG_ALIGN(nlmsg_size(payload));
}

static inline int
nlmsg_ok(const struct nlmsghdr *nlh, int remaining)
{
    return (remaining >= (int) sizeof(struct nlmsghdr) && nlh->nlmsg_len >= sizeof(struct nlmsghdr)
            && nlh->nlmsg_len <= remaining);
}

static inline struct nlmsghdr *
nlmsg_next(struct nlmsghdr *nlh, int *remaining)
{
    int totlen = NLMSG_ALIGN(nlh->nlmsg_len);

    *remaining -= totlen;

    return NM_CAST_ALIGN(struct nlmsghdr, (((char *) nlh) + totlen));
}

int  nlmsg_get_proto(struct nl_msg *msg);
void nlmsg_set_proto(struct nl_msg *msg, int protocol);

void nlmsg_set_src(struct nl_msg *msg, struct sockaddr_nl *addr);

struct ucred *nlmsg_get_creds(struct nl_msg *msg);
void          nlmsg_set_creds(struct nl_msg *msg, struct ucred *creds);

NM_AUTO_DEFINE_FCN0(struct nl_msg *, _nm_auto_nl_msg_cleanup, nlmsg_free);
#define nm_auto_nlmsg nm_auto(_nm_auto_nl_msg_cleanup)

static inline const struct nlmsghdr *
nlmsg_undata(const void *data)
{
    /* from the data, get back the header. It's the inverse of nlmsg_data(). */
    return (void *) (((unsigned char *) data) - NLMSG_HDRLEN);
}

static inline void *
nlmsg_data(const struct nlmsghdr *nlh)
{
    return ((unsigned char *) nlh) + NLMSG_HDRLEN;
}

static inline void *
nlmsg_tail(const struct nlmsghdr *nlh)
{
    return ((unsigned char *) nlh) + NLMSG_ALIGN(nlh->nlmsg_len);
}

struct nlmsghdr *nlmsg_hdr(const struct nl_msg *n);

static inline int
nlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen)
{
    if (nlh->nlmsg_len < nlmsg_size(hdrlen))
        return 0;

    return 1;
}

static inline int
nlmsg_datalen(const struct nlmsghdr *nlh)
{
    return nlh->nlmsg_len - NLMSG_HDRLEN;
}

static inline int
nlmsg_attrlen(const struct nlmsghdr *nlh, int hdrlen)
{
    return NM_MAX((int) (nlmsg_datalen(nlh) - NLMSG_ALIGN(hdrlen)), 0);
}

static inline struct nlattr *
nlmsg_attrdata(const struct nlmsghdr *nlh, int hdrlen)
{
    char *data = nlmsg_data(nlh);

    return NM_CAST_ALIGN(struct nlattr, (data + NLMSG_ALIGN(hdrlen)));
}

static inline struct nlattr *
nlmsg_find_attr(struct nlmsghdr *nlh, int hdrlen, int attrtype)
{
    return nla_find(nlmsg_attrdata(nlh, hdrlen), nlmsg_attrlen(nlh, hdrlen), attrtype);
}

int nlmsg_parse_error(const struct nlmsghdr *nlh, const char **out_extack_msg);

int nlmsg_parse(const struct nlmsghdr   *nlh,
                int                      hdrlen,
                struct nlattr           *tb[],
                int                      maxtype,
                const struct nla_policy *policy);

#define nlmsg_parse_arr(nlh, hdrlen, tb, policy)                            \
    ({                                                                      \
        _nl_static_assert_tb((tb), (policy));                               \
        G_STATIC_ASSERT_EXPR((hdrlen) >= 0);                                \
                                                                            \
        nlmsg_parse((nlh), (hdrlen), (tb), G_N_ELEMENTS(tb) - 1, (policy)); \
    })

struct nlmsghdr *nlmsg_put(struct nl_msg *n,
                           uint32_t       pid,
                           uint32_t       seq,
                           uint16_t       type,
                           uint32_t       payload,
                           uint16_t       flags);

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

typedef enum {
    NL_SOCKET_FLAGS_NONE             = 0,
    NL_SOCKET_FLAGS_NONBLOCK         = 0x1,
    NL_SOCKET_FLAGS_PASSCRED         = 0x2,
    NL_SOCKET_FLAGS_PKTINFO          = 0x4,
    NL_SOCKET_FLAGS_DISABLE_MSG_PEEK = 0x8,

    _NL_SOCKET_FLAGS_ALL = (NL_SOCKET_FLAGS_DISABLE_MSG_PEEK << 1) - 1,
} NLSocketFlags;

#define NL_AUTO_PORT 0
#define NL_AUTO_SEQ  0

struct nl_sock;

int nl_socket_new(struct nl_sock **out_sk,
                  int              protocol,
                  NLSocketFlags    flags,
                  int              bufsize_rx,
                  int              bufsize_tx);

void nl_socket_free(struct nl_sock *sk);

NM_AUTO_DEFINE_FCN0(struct nl_sock *, _nm_auto_nlsock, nl_socket_free);
#define nm_auto_nlsock nm_auto(_nm_auto_nlsock)

int nl_socket_get_fd(const struct nl_sock *sk);

struct sockaddr_nl *nlmsg_get_dst(struct nl_msg *msg);

size_t nl_socket_get_msg_buf_size(struct nl_sock *sk);
int    nl_socket_set_msg_buf_size(struct nl_sock *sk, size_t bufsize);

int nl_socket_set_buffer_size(struct nl_sock *sk, int rxbuf, int txbuf);

int nl_socket_set_passcred(struct nl_sock *sk, int state);

int nl_socket_set_pktinfo(struct nl_sock *sk, int state);

int nl_socket_set_nonblocking(const struct nl_sock *sk);

uint32_t nl_socket_get_local_port(const struct nl_sock *sk);

int nl_socket_add_memberships(struct nl_sock *sk, int group, ...);

int nl_connect(struct nl_sock *sk, int protocol);

int nl_recv(struct nl_sock     *sk,
            unsigned char      *buf0,
            size_t              buf0_len,
            struct sockaddr_nl *nla,
            unsigned char     **buf,
            struct ucred       *out_creds,
            gboolean           *out_creds_has,
            uint32_t           *out_pktinfo_group,
            gboolean           *out_pktinfo_has);

int nl_send(struct nl_sock *sk, struct nl_msg *msg);

int nl_send_auto(struct nl_sock *sk, struct nl_msg *msg);

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

enum nl_cb_action {
    /* Proceed with wathever would come next */
    NL_OK,
    /* Skip this message */
    NL_SKIP,
    /* Stop parsing altogether and discard remaining messages */
    NL_STOP,
};

typedef int (*nl_recvmsg_msg_cb_t)(const struct nl_msg *msg, void *arg);

typedef int (*nl_recvmsg_err_cb_t)(const struct sockaddr_nl *nla,
                                   const struct nlmsgerr    *nlerr,
                                   void                     *arg);

struct nl_cb {
    nl_recvmsg_msg_cb_t valid_cb;
    void               *valid_arg;

    nl_recvmsg_msg_cb_t finish_cb;
    void               *finish_arg;

    nl_recvmsg_msg_cb_t ack_cb;
    void               *ack_arg;

    nl_recvmsg_err_cb_t err_cb;
    void               *err_arg;
};

int nl_sendmsg(struct nl_sock *sk, struct nl_msg *msg, struct msghdr *hdr);

int nl_send_iovec(struct nl_sock *sk, struct nl_msg *msg, struct iovec *iov, unsigned iovlen);

void nl_complete_msg(struct nl_sock *sk, struct nl_msg *msg);

int nl_recvmsgs(struct nl_sock *sk, const struct nl_cb *cb);

int nl_wait_for_ack(struct nl_sock *sk, const struct nl_cb *cb);

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

extern const struct nla_policy genl_ctrl_policy[8];

void                    *genlmsg_put(struct nl_msg *msg,
                                     uint32_t       port,
                                     uint32_t       seq,
                                     uint16_t       family,
                                     uint32_t       hdrlen,
                                     uint16_t       flags,
                                     uint8_t        cmd,
                                     uint8_t        version);
void                    *genlmsg_data(const struct genlmsghdr *gnlh);
void                    *genlmsg_user_hdr(const struct genlmsghdr *gnlh);
const struct genlmsghdr *genlmsg_hdr(const struct nlmsghdr *nlh);
void                    *genlmsg_user_data(const struct genlmsghdr *gnlh, const int hdrlen);
struct nlattr           *genlmsg_attrdata(const struct genlmsghdr *gnlh, int hdrlen);
int                      genlmsg_len(const struct genlmsghdr *gnlh);
int                      genlmsg_attrlen(const struct genlmsghdr *gnlh, int hdrlen);
int                      genlmsg_valid_hdr(const struct nlmsghdr *nlh, int hdrlen);

int genlmsg_parse(const struct nlmsghdr   *nlh,
                  int                      hdrlen,
                  struct nlattr           *tb[],
                  int                      maxtype,
                  const struct nla_policy *policy);

#define genlmsg_parse_arr(nlh, hdrlen, tb, policy)                            \
    ({                                                                        \
        _nl_static_assert_tb((tb), (policy));                                 \
        G_STATIC_ASSERT_EXPR((hdrlen) >= 0);                                  \
                                                                              \
        genlmsg_parse((nlh), (hdrlen), (tb), G_N_ELEMENTS(tb) - 1, (policy)); \
    })

int genl_ctrl_resolve(struct nl_sock *sk, const char *name);

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

#endif /* __NM_NETLINK_H__ */
