// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
#ifndef __YNL_C_H
#define __YNL_C_H 1

#include <stddef.h>
#include <libmnl/libmnl.h>
#include <linux/genetlink.h>
#include <linux/types.h>

struct mnl_socket;
struct nlmsghdr;

/*
 * User facing code
 */

struct ynl_ntf_base_type;
struct ynl_ntf_info;
struct ynl_sock;

enum ynl_error_code {
	YNL_ERROR_NONE = 0,
	__YNL_ERRNO_END = 4096,
	YNL_ERROR_INTERNAL,
	YNL_ERROR_EXPECT_ACK,
	YNL_ERROR_EXPECT_MSG,
	YNL_ERROR_UNEXPECT_MSG,
	YNL_ERROR_ATTR_MISSING,
	YNL_ERROR_ATTR_INVALID,
	YNL_ERROR_UNKNOWN_NTF,
	YNL_ERROR_INV_RESP,
};

/**
 * struct ynl_error - error encountered by YNL
 * @code:	errno (low values) or YNL error code (enum ynl_error_code)
 * @attr_offs:	offset of bad attribute (for very advanced users)
 * @msg:	error message
 *
 * Error information for when YNL operations fail.
 * Users should interact with the err member of struct ynl_sock directly.
 * The main exception to that rule is ynl_sock_create().
 */
struct ynl_error {
	enum ynl_error_code code;
	unsigned int attr_offs;
	char msg[512];
};

/**
 * struct ynl_family - YNL family info
 * Family description generated by codegen. Pass to ynl_sock_create().
 */
struct ynl_family {
/* private: */
	const char *name;
	const struct ynl_ntf_info *ntf_info;
	unsigned int ntf_info_size;
};

/**
 * struct ynl_sock - YNL wrapped netlink socket
 * @err: YNL error descriptor, cleared on every request.
 */
struct ynl_sock {
	struct ynl_error err;

/* private: */
	const struct ynl_family *family;
	struct mnl_socket *sock;
	__u32 seq;
	__u32 portid;
	__u16 family_id;

	unsigned int n_mcast_groups;
	struct {
		unsigned int id;
		char name[GENL_NAMSIZ];
	} *mcast_groups;

	struct ynl_ntf_base_type *ntf_first;
	struct ynl_ntf_base_type **ntf_last_next;

	struct nlmsghdr *nlh;
	struct ynl_policy_nest *req_policy;
	unsigned char *tx_buf;
	unsigned char *rx_buf;
	unsigned char raw_buf[];
};

struct ynl_sock *
ynl_sock_create(const struct ynl_family *yf, struct ynl_error *e);
void ynl_sock_destroy(struct ynl_sock *ys);

#define ynl_dump_foreach(dump, iter)					\
	for (typeof(dump->obj) *iter = &dump->obj;			\
	     !ynl_dump_obj_is_last(iter);				\
	     iter = ynl_dump_obj_next(iter))

int ynl_subscribe(struct ynl_sock *ys, const char *grp_name);
int ynl_socket_get_fd(struct ynl_sock *ys);
int ynl_ntf_check(struct ynl_sock *ys);

/**
 * ynl_has_ntf() - check if socket has *parsed* notifications
 * @ys: active YNL socket
 *
 * Note that this does not take into account notifications sitting
 * in netlink socket, just the notifications which have already been
 * read and parsed (e.g. during a ynl_ntf_check() call).
 */
static inline bool ynl_has_ntf(struct ynl_sock *ys)
{
	return ys->ntf_last_next != &ys->ntf_first;
}
struct ynl_ntf_base_type *ynl_ntf_dequeue(struct ynl_sock *ys);

void ynl_ntf_free(struct ynl_ntf_base_type *ntf);

/*
 * YNL internals / low level stuff
 */

/* Generic mnl helper code */

enum ynl_policy_type {
	YNL_PT_REJECT = 1,
	YNL_PT_IGNORE,
	YNL_PT_NEST,
	YNL_PT_FLAG,
	YNL_PT_BINARY,
	YNL_PT_U8,
	YNL_PT_U16,
	YNL_PT_U32,
	YNL_PT_U64,
	YNL_PT_NUL_STR,
};

struct ynl_policy_attr {
	enum ynl_policy_type type;
	unsigned int len;
	const char *name;
	struct ynl_policy_nest *nest;
};

struct ynl_policy_nest {
	unsigned int max_attr;
	struct ynl_policy_attr *table;
};

struct ynl_parse_arg {
	struct ynl_sock *ys;
	struct ynl_policy_nest *rsp_policy;
	void *data;
};

struct ynl_dump_list_type {
	struct ynl_dump_list_type *next;
	unsigned char data[] __attribute__ ((aligned (8)));
};
extern struct ynl_dump_list_type *YNL_LIST_END;

static inline bool ynl_dump_obj_is_last(void *obj)
{
	unsigned long uptr = (unsigned long)obj;

	uptr -= offsetof(struct ynl_dump_list_type, data);
	return uptr == (unsigned long)YNL_LIST_END;
}

static inline void *ynl_dump_obj_next(void *obj)
{
	unsigned long uptr = (unsigned long)obj;
	struct ynl_dump_list_type *list;

	uptr -= offsetof(struct ynl_dump_list_type, data);
	list = (void *)uptr;
	uptr = (unsigned long)list->next;
	uptr += offsetof(struct ynl_dump_list_type, data);

	return (void *)uptr;
}

struct ynl_ntf_base_type {
	__u16 family;
	__u8 cmd;
	struct ynl_ntf_base_type *next;
	void (*free)(struct ynl_ntf_base_type *ntf);
	unsigned char data[] __attribute__ ((aligned (8)));
};

extern mnl_cb_t ynl_cb_array[NLMSG_MIN_TYPE];

struct nlmsghdr *
ynl_gemsg_start_req(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version);
struct nlmsghdr *
ynl_gemsg_start_dump(struct ynl_sock *ys, __u32 id, __u8 cmd, __u8 version);

int ynl_attr_validate(struct ynl_parse_arg *yarg, const struct nlattr *attr);

int ynl_recv_ack(struct ynl_sock *ys, int ret);
int ynl_cb_null(const struct nlmsghdr *nlh, void *data);

/* YNL specific helpers used by the auto-generated code */

struct ynl_req_state {
	struct ynl_parse_arg yarg;
	mnl_cb_t cb;
	__u32 rsp_cmd;
};

struct ynl_dump_state {
	struct ynl_sock *ys;
	struct ynl_policy_nest *rsp_policy;
	void *first;
	struct ynl_dump_list_type *last;
	size_t alloc_sz;
	mnl_cb_t cb;
	__u32 rsp_cmd;
};

struct ynl_ntf_info {
	struct ynl_policy_nest *policy;
	mnl_cb_t cb;
	size_t alloc_sz;
	void (*free)(struct ynl_ntf_base_type *ntf);
};

int ynl_exec(struct ynl_sock *ys, struct nlmsghdr *req_nlh,
	     struct ynl_req_state *yrs);
int ynl_exec_dump(struct ynl_sock *ys, struct nlmsghdr *req_nlh,
		  struct ynl_dump_state *yds);

void ynl_error_unknown_notification(struct ynl_sock *ys, __u8 cmd);
int ynl_error_parse(struct ynl_parse_arg *yarg, const char *msg);

#endif