/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2020-2023 Brett Sheffield <bacs@librecast.net> */

#ifndef MLD_PVT_H
#define MLD_PVT_H 1

#include "config.h"
#include <mld.h>
#include "job.h"
#include <assert.h>
#ifdef USE_BLOOM_FILTER
# include "vec.h"
#endif

#define MLD_DEBUG 1
#define BUFSIZE 1500
#define IFACE_MAX UCHAR_MAX
#ifndef MAXMIFS
# define MAXMIFS 32
#endif

#ifndef MLD_LISTENER_DONE
#define MLD_LISTENER_DONE MLD_LISTENER_REDUCTION
#endif

/* number of nanoseconds to wait for MLD Listen Thread to initialize */
#define MLD_LISTEN_THREAD_STARTUP 100000000L

#define HAVE_NETLINK __linux__

#ifndef aload
# define aload(x) __atomic_load_n((x), __ATOMIC_ACQUIRE)
#endif
#ifndef astor
# define astor(x, y) __atomic_store_n((x),(y), __ATOMIC_RELEASE)
#endif
#ifndef aadd
# define aadd(x, y) __atomic_fetch_add((x),(y), __ATOMIC_ACQ_REL)
#endif

typedef enum {
	MLD_NETLINK,
	MLD_LISTEN,
	MLD_STATE,
	MLD_TIMER,
	MLD_WATCH,
	MLD_THREADS
} mld_thread_t;

typedef enum {
	FILTER_MODE_INCLUDE = 1,
	FILTER_MODE_EXCLUDE,
} mld_mode_t;


struct mld_iface_s {
	mld_iface_t *next;
	mld_grp_list_t *grp;
	/* link-local address for this interface */
	struct sockaddr_in6 llink;
	/* Last time we saw a Querier that outranked us on this interface.
	 * we set this when we receive an MLD Query with a lower IPv6 address
	 * than us */
	struct timespec qseen;
	/* Last time we sent a Query on this interface */
	struct timespec qtime;
	/* socket to use for outbound PIM JOINs and MLD Queries */
	int sock;
	/* file descriptor for BPF */
	int bpf;
	/* startup query count */
	char qcount;
	char ifname[IF_NAMESIZE];
	unsigned int ifx;
};

#ifdef USE_BLOOM_FILTER
struct mld_timerjob_s {
	mld_t *mld;
	void (*f)(mld_t *, unsigned int, size_t, uint8_t);
	size_t idx;
	unsigned int iface;
	uint8_t val;
};

struct mld_filter_s {
	/* counted bloom filter for multicast group addresses */
	vec_t   grp[BLOOM_VECTORS];
	/* bloom timer with 8 bit timer values */
	vec_t   t[BLOOM_VECTORS];
};
#else
struct mld_grp_list_s {
	mld_grp_list_t *next;
	struct in6_addr addr;    /* Multicast Address */
	struct timespec expires; /* record expires */
	struct timespec qlast;   /* Last MLD Specific Query sent to this group + iface */
};
#endif

// IPv6MulticastListen ( socket, interface, IPv6 multicast address, filter mode, source list )

// per-socket state
// (interface, IPv6 multicast address, filter mode, source list)
//
// per-interface state
// (IPv6 multicast address, filter mode, source list)

typedef enum {
	MLD_TXN_NOOP,
	MLD_TXN_CMP,
	MLD_TXN_ADD,
	MLD_TXN_REFRESH,
	MLD_TXN_DEL
} mld_txn_type;

/* a change in state */
struct mld_txn_s {
	mld_t *mld;
	sem_t done;
	struct in6_addr *addr;   /* Multicast Address */
	struct timespec expires; /* record expires */
	unsigned int ifx;
	int type;
	int err;
	int ret;
};

struct mld_watch_s {
	mld_watch_t *next;
	mld_watch_t *prev;
	mld_t *mld;
	pthread_t tid;
	void (*f)(mld_watch_t *);
	void *arg;
	struct in6_addr grp;
	unsigned int ifx;
	int flags;
};

struct mld_s {
	/* stop if cont points to zero value */
	volatile int *cont;
	job_queue_t *q[MLD_THREADS];
	job_t *job[2]; /* MLD_NETLINK + MLD_LISTEN threads */
	/* raw socket for MLD snooping */
	int sock;
	/* listening socket for the listen thread, to be able to stop receiving */
	int listen_sock;
	/* number of interfaces allocated */
	int len;
	/* interface and address for which we log join/part events */
	unsigned int log_ifnumber;
	const struct sockaddr_in6 *log_addr;
	sem_t sem_mld;   /* MLD listerner thread is ready */
	sem_t sem_state; /* state thread is ready */
#if HAVE_NETLINK
	sem_t sem_netlink; /* netlink thread is ready */
#endif
#ifdef USE_BLOOM_FILTER
	/* counted bloom filter for groups gives us O(1) for insert/query/delete
	 * combined with a bloom timer (is that a thing, or did I just make it
	 * up?) - basically a counted bloom filter where the max is set to the
	 * time in seconds, and we count it down using SIMD instructions
	 */
	/* array of filters */
	mld_filter_t filter[1]; // FIXME - use linked-list as interfaces can change
#else
	mld_iface_t *iface;
#endif
	mld_watch_t *watch;
};

/* Multicast Address Record */
struct mld_addr_rec_s {
	uint8_t         type;    /* Record Type */
	uint8_t         auxl;    /* Aux Data Len */
	uint16_t        srcs;    /* Number of Sources */
	struct in6_addr addr;    /* Multicast Address */
	struct in6_addr src[];   /* Source Address */
};
#ifdef static_assert
static_assert(sizeof(struct mld_addr_rec_s) == 20, "ensure struct doesn't need packing");
#endif

/* Multicast Listener Query Message */
struct mld_query_msg_s {
	uint8_t         type;    /* type = 130 */
	uint8_t         code;
	uint16_t        checksum;
	uint16_t        mrc;     /* Maximum Response Code */
	uint16_t        res1;    /* Reserved */
	struct in6_addr addr;    /* Multicast Address */
	uint8_t		bits;	 /* bits: res2[4] supp[1] qrv[3] */
	uint8_t         qqic;    /* Querier's Query Interval Code */
	uint16_t        srcs;    /* Number of Sources */
};

struct mld_msg_s {
	struct msghdr msgh;
	struct cmsghdr *cmsgh;
	struct iovec iov[1];
	struct sockaddr_in6 dst;
	char *cmsgbuf;
	mld_query_msg_t qmsg;
	socklen_t extlen;
};

/* add interface to mld */
int mld_add_iface(mld_t *mld, unsigned int ifx, char *ifname);

/* delete interface from mld */
int mld_del_iface(mld_t *mld, unsigned int ifx);

/* PART grp (set timer, send MLD Query if applicable) */
int mld_filter_grp_part(mld_t *mld, unsigned int ifx, struct in6_addr *addr);

mld_grp_list_t *mld_get_grp(const mld_t *mld, const unsigned int ifx, const struct in6_addr *grp);

unsigned int interface_index(struct msghdr *msg);

int mld_query_msg(mld_t *mld, unsigned int ifx, struct in6_addr *saddr, mld_msg_t *msg);
void mld_query_msg_free(mld_msg_t *msg);

#endif /* MLD_PVT_H */
