/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2017-2024 Brett Sheffield <bacs@librecast.net> */
/* librecast/net.h - librecast network API */

#ifndef _LIBRECAST_NET_H
#define _LIBRECAST_NET_H

#define _GNU_SOURCE
#include <librecast/types.h>
#include <stdio.h>
#include <sys/socket.h>

#ifndef MSG_WAITFORONE
# define MSG_WAITFORONE 0x00080000
struct mmsghdr {
        struct msghdr msg_hdr;  /* Message header */
        unsigned int  msg_len;  /* Number of received bytes for header */
};
int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
                    int flags, struct timespec *timeout);
#endif

extern int (*lc_msg_logger)(lc_channel_t *, lc_message_t *, void *logdb);

/* create new librecast context and set up environment
 * call lc_ctx_free() when done */
lc_ctx_t *lc_ctx_new(void);

/* destroy librecast context and clean up */
void lc_ctx_free(lc_ctx_t *ctx);

/* acquire read-lock on object. Increments reference count */
lc_ctx_t *lc_ctx_acquire(lc_ctx_t *ctx);
lc_socket_t *lc_socket_acquire(lc_socket_t *sock);
lc_channel_t *lc_channel_acquire(lc_channel_t *chan);

/* release read-lock on object. Decrements reference count.
 * Free()s object if no other readers and marked for deletion */
void lc_ctx_release(lc_ctx_t *ctx);
void lc_socket_release(lc_socket_t *sock);
void lc_channel_release(lc_channel_t *chan);

/* create ctx queue and worker threads */
int lc_ctx_qpool_init(lc_ctx_t *ctx, int nthreads);

/* resize ctx thread pool */
int lc_ctx_qpool_resize(lc_ctx_t *ctx, int nthreads);

/* stop worker threads and free queue */
int lc_ctx_qpool_free(lc_ctx_t *ctx);

/* return pointer to context queue */
q_t *lc_ctx_q(lc_ctx_t *ctx);

/* set debug level */
int lc_ctx_debug(lc_ctx_t *ctx, int debug);

/* set output stream for debug */
FILE *lc_ctx_stream(lc_ctx_t *ctx, FILE *stream);

/* create librecast socket */
lc_socket_t *lc_socket_new(lc_ctx_t *ctx);

/* create a pair of connected librecast sockets */
int lc_socketpair(lc_ctx_t *ctx, lc_socket_t *sock[2]);

/* bind socket to interface with index idx. 0 = ALL (default) */
int lc_socket_bind(lc_socket_t *sock, unsigned int ifx);

/* close socket */
void lc_socket_close(lc_socket_t *sock);

/* Create a new channel by hashing s of length len */
lc_channel_t *lc_channel_nnew(lc_ctx_t *ctx, unsigned char *s, size_t len);

/* Create a new channel from the hash of s which must be a NUL-terminated string */
lc_channel_t *lc_channel_new(lc_ctx_t *ctx, char *s);

/* copy a channel into ctx */
lc_channel_t *lc_channel_copy(lc_ctx_t *ctx, lc_channel_t *chan);

/* create side band channel from base channel by replacing lower 64 bits with band */
lc_channel_t * lc_channel_sideband(lc_channel_t *base, uint64_t band);

/* create side channel from base by hashing additional key material */
lc_channel_t * lc_channel_sidehash(lc_channel_t *base, unsigned char *key, size_t keylen);

/* create random channel */
lc_channel_t *lc_channel_random(lc_ctx_t *ctx);

/* bind channel to socket */
int lc_channel_bind(lc_socket_t *sock, lc_channel_t *chan);

/* unbind channel from socket */
int lc_channel_unbind(lc_channel_t *chan);

/* join librecast channel */
int lc_channel_join(lc_channel_t *chan);

/* leave a librecast channel */
int lc_channel_part(lc_channel_t *chan);

/* set default interface for sockets and channels created with this context */
void lc_ctx_ifx(lc_ctx_t *ctx, unsigned int ifx);

/* set ratelimit on ctx - this will be applied to all subsequently created
 * channels on that ctx */
void lc_ctx_ratelimit(lc_ctx_t *ctx, size_t bps_out, size_t bps_in);

/* set channel rate limits */
void lc_channel_ratelimit(lc_channel_t *chan, size_t bps_out, size_t bps_in);

/* set default coding for sockets and channels created with this context */
int lc_ctx_coding_set(lc_ctx_t *ctx, int coding);

/* set symmetric key for context */
int lc_ctx_getkey(lc_ctx_t *ctx, lc_key_t *key, int type);
int lc_ctx_setkey(lc_ctx_t *ctx, lc_key_t *key, int type);
int lc_ctx_set_sym_key(lc_ctx_t *ctx, unsigned char *key, size_t len);
int lc_ctx_set_pub_key(lc_ctx_t *ctx, unsigned char *key, size_t len);

/* set socket coding */
int lc_socket_coding_set(lc_socket_t *sock, int coding);

/* set channel coding */
int lc_channel_coding_set(lc_channel_t *chan, int coding);

/* set channel key */
int lc_channel_setkey(lc_channel_t *chan, lc_key_t *key, int type);

/* get channel key */
int lc_channel_getkey(lc_channel_t *chan, lc_key_t *key, int type);

/* set symmetric channel key */
int lc_channel_set_sym_key(lc_channel_t *chan, unsigned char *key, size_t len);

/* set public channel key */
int lc_channel_set_pub_key(lc_channel_t *chan, unsigned char *key, size_t len);

/* start a thread to answer NACKs and replay messages as required */
int lc_channel_nack_handler(lc_channel_t *chan, int n_seconds);

/* similar to lc_channel_nack_handler but with an explicit NACK thread instead
 * of the built-in one */
int lc_channel_nack_handler_thr(lc_channel_t *chan, int n_seconds, void *(*nack_thread)(void *));

/* logs an outgoing message explicitely for the NACK thread; if lc_channel_nack_handler()
 * has not been called this is a NOP; not required if using lc_msg_send() to send messages */
int lc_channel_nack_add_log(lc_channel_t *chan, const void *buf, size_t len, lc_seq_t seq);

/* configure the channel to check for gaps in sequence number and send NACKs */
int lc_channel_detect_gaps(lc_channel_t *chan);

/* check for sequence number gaps and send NACKs if required; if lc_channel_detect_gaps()
 * has not been called this is a NOP; not required if using lc_msg_recv() or lc_socket_listen()
 * to receive messages */
int lc_channel_check_seqno(lc_channel_t *chan, lc_seq_t seq);

/* blocking socket recv() */
ssize_t lc_socket_recv(lc_socket_t *sock, void *buf, size_t len, int flags);

/* non-blocking socket listener, with callbacks */
int lc_socket_listen(lc_socket_t *sock, void (*callback_msg)(lc_message_t*),
			                void (*callback_err)(int));

/* stop listening on socket */
int lc_socket_listen_cancel(lc_socket_t *sock);

/* send to all channels bound to a socket */
ssize_t lc_socket_send(lc_socket_t *sock, const void *buf, size_t len, int flags);
ssize_t lc_socket_sendmsg(lc_socket_t *sock, struct msghdr *msg, int flags);

/* send to channel. Channel must be bound to Librecast socket with
 * lc_channel_bind() first. */
ssize_t lc_channel_send(lc_channel_t *chan, const void *buf, size_t len, int flags);
ssize_t lc_channel_sendmsg(lc_channel_t *chan, struct msghdr *msg, int flags);

/* blocking channel recv() */
ssize_t lc_channel_recv(lc_channel_t *chan, void *buf, size_t len, int flags);
ssize_t lc_channel_recvmsg(lc_channel_t *chan, struct msghdr *msg, int flags);

/* blocking message receive */
ssize_t lc_msg_recv(lc_socket_t *sock, lc_message_t *msg);
ssize_t lc_socket_recvmsg(lc_socket_t *sock, struct msghdr *msg, int flags);
int lc_socket_recvmmsg(lc_socket_t *sock, struct mmsghdr *msgvec, unsigned int vlen,
                int flags, struct timespec *timeout);

/* send a message to a channel */
ssize_t lc_msg_send(lc_channel_t *chan, lc_message_t *msg);
ssize_t lc_msg_sendto(int sock, const void *buf, size_t len, struct sockaddr_in6 *addr, int flags);

/* get/set socket options */
int lc_socket_getopt(lc_socket_t *sock, int optname, void *optval, socklen_t *optlen);
int lc_socket_setopt(lc_socket_t *sock, int optname, const void *optval, socklen_t optlen);

/* turn socket loopback on (val = 1) or off (val = 0)*/
int lc_socket_loop(lc_socket_t *sock, int val);

/* set multicast TTL (hop limit) for this socket to val */
int lc_socket_ttl(lc_socket_t *sock, int val);

/* return router for socket */
lc_router_t *lc_socket_router(lc_socket_t *sock);

/* manage message structures */

/* initialize message structure */
void *lc_msg_init(lc_message_t *msg);

/* allocate message struture of size len */
int lc_msg_init_size(lc_message_t *msg, size_t len);

/* initialize message from supplied data of size len
 * if not NULL, function f will be called to free the structure with hint as an argument */
int lc_msg_init_data(lc_message_t *msg, void *data, size_t len, lc_free_fn_t *f, void *hint);

/* free message */
void lc_msg_free(void *msg);

/* hash message data and source address
 * call with pre-allocated buffer id of size len */
int lc_msg_id(lc_message_t *msg, unsigned char *id, size_t len);

/* return pointer to message data */
void *lc_msg_data(lc_message_t *msg);

/* get message attributes */
int lc_msg_get(lc_message_t *msg, lc_msg_attr_t attr, void **value);

/* set message attributes */
int lc_msg_set(lc_message_t *msg, lc_msg_attr_t attr, void *value);

/* return raw network socket */
int lc_socket_raw(lc_socket_t *sock);
int lc_channel_socket_raw(lc_channel_t *chan);

/* return context for socket/channel */
lc_ctx_t *lc_socket_ctx(lc_socket_t *sock);
lc_ctx_t *lc_channel_ctx(lc_channel_t *chan);

/* find socket by file descriptor */
lc_socket_t * lc_socket_by_fd(lc_ctx_t *ctx, int fd);

#ifdef HAVE_LIBLCRQ
#include <lcrq.h>
/* return lcrq handle */
rq_t *lc_channel_rq(lc_channel_t *chan);
# if HAVE_RQ_OTI
int lc_channel_oti_peek(lc_channel_t *chan, rq_oti_t *oti, rq_scheme_t *scheme);
# endif
#endif

/* return socket bound to this channel */
lc_socket_t *lc_channel_socket(lc_channel_t *chan);

/* return socket address for this channel */
struct sockaddr_in6 *lc_channel_sockaddr(lc_channel_t *chan);

/* return struct in6_addr for this channel */
struct in6_addr *lc_channel_in6addr(lc_channel_t *chan);

/* return channel hash */
unsigned char *lc_channel_get_hash(lc_channel_t *chan);

/* search context and return channel */
lc_channel_t *lc_channel_by_address(lc_ctx_t *lctx, struct in6_addr *addr);
lc_channel_t *lc_channel_by_hash(lc_ctx_t *lctx, unsigned char *hash, size_t hashlen);

/* search context for channel by both hash and bound socket, as there can be more than
    one channel with the same hash. */
lc_channel_t *lc_channel_socket_by_hash(lc_socket_t *sock, unsigned char *hash, size_t hashlen);

/* create IPv6 multicast addr from supplied hash and flags */
void lc_hashtoaddr(struct in6_addr *addr, unsigned char *hash, size_t hashlen, unsigned char flags);

/* Create a new channel using supplied hash, flags and port */
lc_channel_t * lc_channel_hash(lc_ctx_t *ctx, unsigned char *hash, size_t hashlen,
		unsigned char flags, short port);

/* create new channel from grp address and service */
lc_channel_t * lc_channel_init(lc_ctx_t *ctx, struct sockaddr_in6 *sa);

/* convenience function for calling lc_channel_init() */
lc_channel_t *lc_channel_init_grp(lc_ctx_t *ctx, struct in6_addr *grp, short port);

/* free channel */
void lc_channel_free(lc_channel_t *chan);

/* get some random bytes */
int lc_getrandom(void *buf, size_t buflen);

/* add/del hash to/from socket Outgoing Interface List (OIL) filter
 * return 0 on success, -1 on error/not found, setting errno:
 *   ENOENT - not found */
int lc_socket_oil_add(lc_socket_t *sock, unsigned char *hash);
int lc_socket_oil_del(lc_socket_t *sock, unsigned char *hash);

/* return 0 if hash is found in socket Outgoing Interface List (OIL)
 * returns -1 on error/not found, setting errno:
 *   ENOENT - not found */
int lc_socket_oil_cmp(lc_socket_t *sock, unsigned char *hash);

#endif /* _LIBRECAST_NET_H */
