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

#include <stdbool.h>
#include <stddef.h>
#include <linux/types.h>

struct ynl_parse_arg;

/*
 * YNL internals / low level stuff
 */

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_UINT,
	YNL_PT_NUL_STR,
	YNL_PT_BITFIELD32,
};

enum ynl_parse_result {
	YNL_PARSE_CB_ERROR = -1,
	YNL_PARSE_CB_STOP = 0,
	YNL_PARSE_CB_OK = 1,
};

#define YNL_SOCKET_BUFFER_SIZE		(1 << 17)

#define YNL_ARRAY_SIZE(array)	(sizeof(array) ?			\
				 sizeof(array) / sizeof(array[0]) : 0)

typedef int (*ynl_parse_cb_t)(const struct nlmsghdr *nlh,
			      struct ynl_parse_arg *yarg);

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)));
};

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);

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

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

struct ynl_dump_state {
	struct ynl_parse_arg yarg;
	void *first;
	struct ynl_dump_list_type *last;
	size_t alloc_sz;
	ynl_parse_cb_t cb;
	__u32 rsp_cmd;
};

struct ynl_ntf_info {
	struct ynl_policy_nest *policy;
	ynl_parse_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);

/* Netlink message handling helpers */

#define YNL_MSG_OVERFLOW	1

static inline struct nlmsghdr *ynl_nlmsg_put_header(void *buf)
{
	struct nlmsghdr *nlh = buf;

	memset(nlh, 0, sizeof(*nlh));
	nlh->nlmsg_len = NLMSG_HDRLEN;

	return nlh;
}

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

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

static inline void *
ynl_nlmsg_data_offset(const struct nlmsghdr *nlh, unsigned int offset)
{
	return (unsigned char *)nlh + NLMSG_HDRLEN + offset;
}

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

static inline void *
ynl_nlmsg_put_extra_header(struct nlmsghdr *nlh, unsigned int size)
{
	void *tail = ynl_nlmsg_end_addr(nlh);

	nlh->nlmsg_len += NLMSG_ALIGN(size);
	return tail;
}

/* Netlink attribute helpers */

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

static inline unsigned int ynl_attr_data_len(const struct nlattr *attr)
{
	return attr->nla_len - NLA_HDRLEN;
}

static inline void *ynl_attr_data(const struct nlattr *attr)
{
	return (unsigned char *)attr + NLA_HDRLEN;
}

static inline void *ynl_attr_data_end(const struct nlattr *attr)
{
	return ynl_attr_data(attr) + ynl_attr_data_len(attr);
}

#define ynl_attr_for_each(attr, nlh, fixed_hdr_sz)			\
	for ((attr) = ynl_attr_first(nlh, (nlh)->nlmsg_len,		\
				     NLMSG_HDRLEN + fixed_hdr_sz); attr; \
	     (attr) = ynl_attr_next(ynl_nlmsg_end_addr(nlh), attr))

#define ynl_attr_for_each_nested(attr, outer)				\
	for ((attr) = ynl_attr_first(outer, outer->nla_len,		\
				     sizeof(struct nlattr)); attr;	\
	     (attr) = ynl_attr_next(ynl_attr_data_end(outer), attr))

#define ynl_attr_for_each_payload(start, len, attr)			\
	for ((attr) = ynl_attr_first(start, len, 0); attr;		\
	     (attr) = ynl_attr_next(start + len, attr))

static inline struct nlattr *
ynl_attr_if_good(const void *end, struct nlattr *attr)
{
	if (attr + 1 > (const struct nlattr *)end)
		return NULL;
	if (ynl_attr_data_end(attr) > end)
		return NULL;
	return attr;
}

static inline struct nlattr *
ynl_attr_next(const void *end, const struct nlattr *prev)
{
	struct nlattr *attr;

	attr = (void *)((char *)prev + NLA_ALIGN(prev->nla_len));
	return ynl_attr_if_good(end, attr);
}

static inline struct nlattr *
ynl_attr_first(const void *start, size_t len, size_t skip)
{
	struct nlattr *attr;

	attr = (void *)((char *)start + NLMSG_ALIGN(skip));
	return ynl_attr_if_good(start + len, attr);
}

static inline bool
__ynl_attr_put_overflow(struct nlmsghdr *nlh, size_t size)
{
	bool o;

	/* ynl_msg_start() stashed buffer length in nlmsg_pid. */
	o = nlh->nlmsg_len + NLA_HDRLEN + NLMSG_ALIGN(size) > nlh->nlmsg_pid;
	if (o)
		/* YNL_MSG_OVERFLOW is < NLMSG_HDRLEN, all subsequent checks
		 * are guaranteed to fail.
		 */
		nlh->nlmsg_pid = YNL_MSG_OVERFLOW;
	return o;
}

static inline struct nlattr *
ynl_attr_nest_start(struct nlmsghdr *nlh, unsigned int attr_type)
{
	struct nlattr *attr;

	if (__ynl_attr_put_overflow(nlh, 0))
		return ynl_nlmsg_end_addr(nlh) - NLA_HDRLEN;

	attr = ynl_nlmsg_end_addr(nlh);
	attr->nla_type = attr_type | NLA_F_NESTED;
	nlh->nlmsg_len += NLA_HDRLEN;

	return attr;
}

static inline void
ynl_attr_nest_end(struct nlmsghdr *nlh, struct nlattr *attr)
{
	attr->nla_len = (char *)ynl_nlmsg_end_addr(nlh) - (char *)attr;
}

static inline void
ynl_attr_put(struct nlmsghdr *nlh, unsigned int attr_type,
	     const void *value, size_t size)
{
	struct nlattr *attr;

	if (__ynl_attr_put_overflow(nlh, size))
		return;

	attr = ynl_nlmsg_end_addr(nlh);
	attr->nla_type = attr_type;
	attr->nla_len = NLA_HDRLEN + size;

	memcpy(ynl_attr_data(attr), value, size);

	nlh->nlmsg_len += NLMSG_ALIGN(attr->nla_len);
}

static inline void
ynl_attr_put_str(struct nlmsghdr *nlh, unsigned int attr_type, const char *str)
{
	struct nlattr *attr;
	size_t len;

	len = strlen(str);
	if (__ynl_attr_put_overflow(nlh, len))
		return;

	attr = ynl_nlmsg_end_addr(nlh);
	attr->nla_type = attr_type;

	strcpy(ynl_attr_data(attr), str);
	attr->nla_len = NLA_HDRLEN + NLA_ALIGN(len);

	nlh->nlmsg_len += NLMSG_ALIGN(attr->nla_len);
}

static inline const char *ynl_attr_get_str(const struct nlattr *attr)
{
	return (const char *)ynl_attr_data(attr);
}

static inline __s8 ynl_attr_get_s8(const struct nlattr *attr)
{
	return *(__s8 *)ynl_attr_data(attr);
}

static inline __s16 ynl_attr_get_s16(const struct nlattr *attr)
{
	return *(__s16 *)ynl_attr_data(attr);
}

static inline __s32 ynl_attr_get_s32(const struct nlattr *attr)
{
	return *(__s32 *)ynl_attr_data(attr);
}

static inline __s64 ynl_attr_get_s64(const struct nlattr *attr)
{
	__s64 tmp;

	memcpy(&tmp, (unsigned char *)(attr + 1), sizeof(tmp));
	return tmp;
}

static inline __u8 ynl_attr_get_u8(const struct nlattr *attr)
{
	return *(__u8 *)ynl_attr_data(attr);
}

static inline __u16 ynl_attr_get_u16(const struct nlattr *attr)
{
	return *(__u16 *)ynl_attr_data(attr);
}

static inline __u32 ynl_attr_get_u32(const struct nlattr *attr)
{
	return *(__u32 *)ynl_attr_data(attr);
}

static inline __u64 ynl_attr_get_u64(const struct nlattr *attr)
{
	__u64 tmp;

	memcpy(&tmp, (unsigned char *)(attr + 1), sizeof(tmp));
	return tmp;
}

static inline void
ynl_attr_put_s8(struct nlmsghdr *nlh, unsigned int attr_type, __s8 value)
{
	ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}

static inline void
ynl_attr_put_s16(struct nlmsghdr *nlh, unsigned int attr_type, __s16 value)
{
	ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}

static inline void
ynl_attr_put_s32(struct nlmsghdr *nlh, unsigned int attr_type, __s32 value)
{
	ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}

static inline void
ynl_attr_put_s64(struct nlmsghdr *nlh, unsigned int attr_type, __s64 value)
{
	ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}

static inline void
ynl_attr_put_u8(struct nlmsghdr *nlh, unsigned int attr_type, __u8 value)
{
	ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}

static inline void
ynl_attr_put_u16(struct nlmsghdr *nlh, unsigned int attr_type, __u16 value)
{
	ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}

static inline void
ynl_attr_put_u32(struct nlmsghdr *nlh, unsigned int attr_type, __u32 value)
{
	ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}

static inline void
ynl_attr_put_u64(struct nlmsghdr *nlh, unsigned int attr_type, __u64 value)
{
	ynl_attr_put(nlh, attr_type, &value, sizeof(value));
}

static inline __u64 ynl_attr_get_uint(const struct nlattr *attr)
{
	switch (ynl_attr_data_len(attr)) {
	case 4:
		return ynl_attr_get_u32(attr);
	case 8:
		return ynl_attr_get_u64(attr);
	default:
		return 0;
	}
}

static inline __s64 ynl_attr_get_sint(const struct nlattr *attr)
{
	switch (ynl_attr_data_len(attr)) {
	case 4:
		return ynl_attr_get_s32(attr);
	case 8:
		return ynl_attr_get_s64(attr);
	default:
		return 0;
	}
}

static inline void
ynl_attr_put_uint(struct nlmsghdr *nlh, __u16 type, __u64 data)
{
	if ((__u32)data == (__u64)data)
		ynl_attr_put_u32(nlh, type, data);
	else
		ynl_attr_put_u64(nlh, type, data);
}

static inline void
ynl_attr_put_sint(struct nlmsghdr *nlh, __u16 type, __s64 data)
{
	if ((__s32)data == (__s64)data)
		ynl_attr_put_s32(nlh, type, data);
	else
		ynl_attr_put_s64(nlh, type, data);
}
#endif
