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

#define _GNU_SOURCE
#include <librecast_pvt.h>
#include <librecast/net.h>
#include <librecast/router.h>
#ifdef HAVE_LIBLCRQ
#include <lcrq.h>
#endif
#ifdef HAVE_MLD
#include <mld.h>
#endif
#include "hash.h"
#include <librecast/sync.h>
#include <librecast/mdex.h>
#include <librecast/q.h>
#include <arpa/inet.h>
#include <assert.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>

/* define macro for lcrq < 0.1.0 */
#ifndef  rq_pidset
# define rq_pidset(sbn, esi) ((sbn << 24) | (esi & 0x00ffffff));
#endif

lc_ctx_t *ctx_list = NULL;

static void lc_op_data_handler(lc_socket_call_t *sc, lc_message_t *msg);
static void lc_op_ping_handler(lc_socket_call_t *sc, lc_message_t *msg);
static void lc_op_pong_handler(lc_socket_call_t *sc, lc_message_t *msg);

int (*lc_msg_logger)(lc_channel_t *, lc_message_t *, void *logdb) = NULL;

void (*lc_op_handler[LC_OP_MAX])(lc_socket_call_t *, lc_message_t *) = {
	lc_op_data_handler,
	lc_op_ping_handler,
	lc_op_pong_handler,
};

int lc_getrandom(void *buf, size_t buflen)
{
	int err, fd;

	if ((fd = open("/dev/urandom", O_RDONLY)) == -1) return -1;
	err = read(fd, buf, buflen);
	close(fd);

	return err;
}

lc_ctx_t *lc_socket_ctx(lc_socket_t *sock)
{
	return sock->ctx;
}

lc_ctx_t *lc_channel_ctx(lc_channel_t *chan)
{
	return chan->ctx;
}

#ifdef HAVE_LIBLCRQ
rq_t *lc_channel_rq(lc_channel_t *chan)
{
	return chan->rq;
}
#endif

lc_socket_t *lc_channel_socket(lc_channel_t *chan)
{
	return chan->sock;
}

unsigned char *lc_channel_get_hash(lc_channel_t *chan)
{
	return chan->hash;
}

struct in6_addr *lc_channel_in6addr(lc_channel_t *chan)
{
	if (!chan->sa.sin6_family) {
		chan->sa.sin6_family = AF_INET6;
		lc_hashtoaddr(&chan->sa.sin6_addr, chan->hash, HASHSIZE, LC_DEFAULT_FLAGS);
	}
	if (!chan->sa.sin6_port) chan->sa.sin6_port = htons(LC_DEFAULT_PORT);
	return &(chan->sa.sin6_addr);
}

struct sockaddr_in6 *lc_channel_sockaddr(lc_channel_t *chan)
{
	return &chan->sa;
}

int lc_channel_socket_raw(lc_channel_t *chan)
{
	return chan->sock->sock;
}

lc_socket_t * lc_socket_by_fd(lc_ctx_t *ctx, int fd)
{
	lc_socket_t *sock = NULL;
	pthread_mutex_lock(&ctx->mtx);
	for (lc_socket_t *s = ctx->sock_list; s; s = s->next) {
		if (s->sock == fd) {
			sock = s;
			break;
		}
	}
	pthread_mutex_unlock(&ctx->mtx);
	return sock;
}

void lc_ctx_ifx(lc_ctx_t *ctx, unsigned int ifx)
{
	ctx->ifx = ifx;
}

void lc_ctx_ratelimit(lc_ctx_t *ctx, size_t bps_out, size_t bps_in)
{
	ctx->bps_out = bps_out;
	ctx->bps_in = bps_in;
}

void lc_channel_ratelimit(lc_channel_t *chan, size_t bps_out, size_t bps_in)
{
	chan->bps_out = bps_out;
	chan->bps_in = bps_in;
}

int lc_socket_raw(lc_socket_t *sock)
{
	return sock->sock;
}

void *lc_msg_data(lc_message_t *msg)
{
	return (msg) ? msg->data: NULL;
}

int lc_msg_get(lc_message_t *msg, lc_msg_attr_t attr, void **value)
{
	if (!msg || !value) return LC_ERROR_INVALID_PARAMS;
	switch (attr) {
	case LC_ATTR_DATA:
		*value = msg->data;
		break;
	case LC_ATTR_LEN:
		*value = (void *)&msg->len;
		break;
	case LC_ATTR_OPCODE:
		*value = (void *)&msg->op;
		break;
	default:
		return LC_ERROR_MSG_ATTR_UNKNOWN;
	}
	return 0;
}

int lc_msg_set(lc_message_t *msg, lc_msg_attr_t attr, void *value)
{
	if (!msg) return LC_ERROR_INVALID_PARAMS;
	switch (attr) {
	case LC_ATTR_DATA:
		msg->data = value;
		break;
	case LC_ATTR_LEN:
		msg->len = *(lc_len_t *)value;
		break;
	case LC_ATTR_OPCODE:
		msg->op = *(lc_opcode_t *)value;
		break;
	default:
		return LC_ERROR_MSG_ATTR_UNKNOWN;
	}
	return 0;
}

int lc_msg_id(lc_message_t *msg, unsigned char *id, size_t len)
{
#ifndef HASH_TYPE
	return (errno = ENOTSUP), -1;
#else
	hash_state state;
	hash_init(&state, NULL, 0, len);
	hash_update(&state, (unsigned char *)msg->data, msg->len);
	hash_update(&state, (unsigned char *)msg->srcaddr, sizeof(struct in6_addr));
	hash_final(&state, id, len);
	return 0;
#endif
}

int lc_socket_getopt(lc_socket_t *sock, int optname, void *optval, socklen_t *optlen)
{
	if (sock->type != LC_SOCK_IN6) return (errno = ENOTSUP), -1;
	return getsockopt(sock->sock, IPPROTO_IPV6, optname, optval, optlen);
}

int lc_socket_setopt(lc_socket_t *sock, int optname, const void *optval, socklen_t optlen)
{
	if (sock->type != LC_SOCK_IN6) return (errno = ENOTSUP), -1;
	return setsockopt(sock->sock, IPPROTO_IPV6, optname, optval, optlen);
}

int lc_socket_loop(lc_socket_t *sock, int val)
{
	if (sock->type != LC_SOCK_IN6) return (errno = ENOTSUP), -1;
	return setsockopt(sock->sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &val, sizeof val);
}

int lc_socket_ttl(lc_socket_t *sock, int val)
{
	sock->ttl = val;
	if (sock->type != LC_SOCK_IN6) return 0;
	return setsockopt(sock->sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &val, sizeof val);
}

lc_router_t *lc_socket_router(lc_socket_t *sock)
{
	return sock->router;
}

static void *_free(void *msg, void *hint)
{
	free(msg);
	return hint;
}

void lc_msg_free(void *arg)
{
	lc_message_t *msg = (lc_message_t *)arg;
	if (msg->free) {
		msg->free(msg->data, msg->hint);
		msg->data = NULL;
	}
}

void *lc_msg_init(lc_message_t *msg)
{
	return memset(msg, 0, sizeof(lc_message_t));
}

int lc_msg_init_data(lc_message_t *msg, void *data, size_t len, lc_free_fn_t *f, void *hint)
{
	lc_msg_init(msg);
	msg->len = len;
	msg->data = data;
	msg->free = f;
	msg->hint = hint;
	return 0;
}

int lc_msg_init_size(lc_message_t *msg, size_t len)
{
	lc_msg_init(msg);
	msg->data = malloc(len);
	if (!msg->data) return -1;
	msg->len = len;
	msg->free = &_free;
	return 0;
}

int lc_ctx_coding_set(lc_ctx_t *ctx, int coding)
{
	return (ctx->coding = coding);
}

int lc_socket_coding_set(lc_socket_t *sock, int coding)
{
	return (sock->ccoding = coding);
}

int lc_channel_coding_set(lc_channel_t *chan, int coding)
{
	if (chan->sock) chan->sock->ccoding = coding;
	return (chan->coding = coding);
}

static int lc_ctx_keygetorset(lc_ctx_t *ctx, lc_key_t *key, int type, int set)
{
	lc_key_t *k;
	switch (type) {
		case LC_CODE_SYMM:
			k = &ctx->key;
			break;
		case LC_CODE_PUBK:
			k = &ctx->pek;
			break;
		default:
			return (errno = EINVAL), -1;
	}
	if (set) {
		k->key = key->key;
		k->keylen = key->keylen;
	}
	else {
		key->key = k->key;
		key->keylen = k->keylen;
	}
	return 0;
}

static int lc_channel_keygetorset(lc_channel_t *chan, lc_key_t *key, int type, int set)
{
	lc_key_t *k;
	switch (type) {
		case LC_CODE_SYMM:
			k = &chan->key;
			break;
		case LC_CODE_PUBK:
			k = &chan->pek;
			break;
		default:
			return (errno = EINVAL), -1;
	}
	if (set) {
		k->key = key->key;
		k->keylen = key->keylen;
	}
	else {
		key->key = k->key;
		key->keylen = k->keylen;
	}
	return 0;
}

int lc_ctx_getkey(lc_ctx_t *ctx, lc_key_t *key, int type)
{
	return lc_ctx_keygetorset(ctx, key, type, 0);
}

int lc_ctx_setkey(lc_ctx_t *ctx, lc_key_t *key, int type)
{
	return lc_ctx_keygetorset(ctx, key, type, 1);
}

int lc_ctx_set_sym_key(lc_ctx_t *ctx, unsigned char *key, size_t len)
{
	lc_key_t k = { .key = key, .keylen = len };
	return lc_ctx_setkey(ctx, &k, LC_CODE_SYMM);
}

int lc_ctx_set_pub_key(lc_ctx_t *ctx, unsigned char *key, size_t len)
{
	lc_key_t k = { .key = key, .keylen = len };
	return lc_ctx_setkey(ctx, &k, LC_CODE_PUBK);
}

int lc_channel_getkey(lc_channel_t *chan, lc_key_t *key, int type)
{
	return lc_channel_keygetorset(chan, key, type, 0);
}

int lc_channel_setkey(lc_channel_t *chan, lc_key_t *key, int type)
{
	return lc_channel_keygetorset(chan, key, type, 1);
}

int lc_channel_set_sym_key(lc_channel_t *chan, unsigned char *key, size_t len)
{
	lc_key_t k = { .key = key, .keylen = len };
	return lc_channel_setkey(chan, &k, LC_CODE_SYMM);
}

int lc_channel_set_pub_key(lc_channel_t *chan, unsigned char *key, size_t len)
{
	lc_key_t k = { .key = key, .keylen = len };
	return lc_channel_setkey(chan, &k, LC_CODE_PUBK);
}

static void lc_close_nack_logger(lc_channel_logging_t *logging)
{
	pthread_cancel(logging->nack_thread);
	pthread_join(logging->nack_thread, NULL);
	pthread_mutex_destroy(&logging->mutex);
	while (logging->oldest) {
		lc_packet_log_t * go = logging->oldest;
		logging->oldest = go->next;
		free(go);
	}
	free(logging);
}

static void free_gap_detect(lc_channel_gap_detect_t * gap_detect) {
	int saveerrno = errno;
	lc_socket_close(gap_detect->sock_nack);
	lc_channel_free(gap_detect->chan_nack);
	free(gap_detect);
	errno = saveerrno;
}

int lc_channel_detect_gaps(lc_channel_t *chan)
{
	lc_channel_gap_detect_t * gap_detect = calloc(1, sizeof(lc_channel_gap_detect_t));
	socklen_t looplen;
	int isloop;
	if (! gap_detect) return -1;
	gap_detect->next = 0;
	gap_detect->chan_nack = lc_channel_sidehash(chan, (void *)SIDE_CHANNEL_NACK, strlen(SIDE_CHANNEL_NACK));
	if (! gap_detect->chan_nack) goto error;
	gap_detect->sock_nack = lc_socket_new(chan->ctx);
	if (! gap_detect->sock_nack) goto error;
	/* if the original channel had loopback we'll need that too */
	looplen = sizeof(isloop);
	if (getsockopt(chan->sock->sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &isloop, &looplen) == 0)
		if (isloop)
			if (lc_socket_loop(gap_detect->sock_nack, 1)) goto error;
	lc_channel_bind(gap_detect->sock_nack, gap_detect->chan_nack);
	if (chan->gap_detect) free_gap_detect(chan->gap_detect);
	chan->gap_detect = gap_detect;
	return 0;
error:
	free_gap_detect(gap_detect);
	return -1;
}

static int do_gap_detection(lc_channel_gap_detect_t *gapd, lc_seq_t seq) {
	/* if we've never received a packet since setting this up, we'll assume packets
	 * before this one have all arrived (no way to know) */
	int duplicate = 0;
	if (! gapd->next) {
		gapd->next = seq + 1;
		gapd->bitmap = ~(uint64_t)0;
		return 0;
	}
	/* see if this is a retrasmission / arrived out of order */
	if (seq < gapd->next) {
		/* arrived out of order, update the bitmap if this is one of the previous
		 * 64 packets;
		 * don't do "if (seq >= gapd->next - 64)" in case the subtraction overflows */
		if (seq + 64 >= gapd->next) {
			if (gapd->bitmap & (uint64_t)1 << (seq + 64 - gapd->next))
				duplicate = 1;
			gapd->bitmap |= (uint64_t)1 << (seq + 64 - gapd->next);
		}
	}
	else {
		/* we'll likely need to shift the bitmap so that it covers seq - 63 to seq;
		 * if any bit we shift out is 0 that is the last chance to send a NACK for it */
		while (gapd->next <= seq) {
			if (! (gapd->bitmap & ((uint64_t)1 << 63))) {
				uint64_t miss = htobe64(gapd->next - 64);
				lc_socket_send(gapd->sock_nack, &miss, sizeof(miss), 0);
			}
			gapd->bitmap <<= 1;
			gapd->next++;
		}
		/* now gapd->bitmap == seq + 1 so bit 0 needs to be set: we've seen seq! */
		gapd->bitmap |= (uint64_t)1;
	}
	/* either way, check if there are any other NACKs we could do with sending; to avoid
	 * sending too many NACKs for the same packet we only check packets older than seq */
	if (~gapd->bitmap) {
		int bit;
		uint64_t mask = (uint64_t)1;
		lc_seq_t mseq = gapd->next - 1;
		for (bit = 0; bit < 64; bit++, mask <<= 1, mseq--) {
			if (mseq < seq && ! (gapd->bitmap & mask)) {
				uint64_t miss = htobe64(mseq);
				if (lc_socket_send(gapd->sock_nack, &miss, sizeof(miss), 0) == -1)
					duplicate = -1;
			}
		}
	}
	return duplicate;
}

int lc_channel_check_seqno(lc_channel_t *chan, lc_seq_t seq)
{
	if (chan && chan->gap_detect)
		return do_gap_detection(chan->gap_detect, seq);
	else return 0;
}

/* remove channel from ctx list */
static void channel_unlink(lc_channel_t *chan)
{
	pthread_mutex_lock(&chan->ctx->mtx);
	lc_channel_t *prev = NULL;
	lc_channel_t *next = aload(&chan->next);
	for (lc_channel_t *p = aload(&chan->ctx->chan_list); p; p = aload(&p->next)) {
		if (p == chan) {
			lc_channel_t **ptr = (prev) ? &prev->next : &chan->ctx->chan_list;
			CAE_loop(ptr, &p, next);
			break;
		}
		prev = p;
	}
	pthread_mutex_unlock(&chan->ctx->mtx);
}

void lc_channel_free(lc_channel_t * chan)
{
	int err = errno;
	if (!chan) return;
#ifdef HAVE_LIBLCRQ
	if (chan->rq) rq_free(chan->rq);
#endif
	if (chan->logging) lc_close_nack_logger(chan->logging);
	if (chan->gap_detect) free_gap_detect(chan->gap_detect);
	channel_unlink(chan);
	int deleted = 0;
	if (CAE(&chan->deleted, &deleted, DELETE_LATER))
		lc_channel_release(chan);
	errno = err;
}

static void chan_ratelimit(lc_channel_t *chan)
{
	uint64_t nss, nse;
	double s, bps;
	struct timespec now = {0};
	nss = chan->byt_reset.tv_nsec + chan->byt_reset.tv_sec * NANO;
	while (1) {
		clock_gettime(CLOCK_TAI, &now);
		nse = now.tv_nsec + now.tv_sec * NANO;
		s = (double)(nse - nss) / NANO;
		bps = chan->byt_out * 8 / s;
		if (bps < chan->bps_out) break;
		usleep(100);
	}
}

static void chan_byt_out(lc_channel_t *chan, size_t byt)
{
	fetch_add(&chan->byt_out, byt);
	fetch_add(&chan->sock->byt_out, byt);
}

static ssize_t chan_sendmsg(lc_channel_t *chan, struct msghdr *msg, int flags)
{
	ssize_t rc = 0;
	if (flags & MSG_DROP) {
		for (int i = 0; i < (int)msg->msg_iovlen; i++) rc += msg->msg_iov[i].iov_len;
		return rc;
	}
	else {
		rc = sendmsg(chan->sock->sock, msg, flags);
	}
	if (rc > 0) chan_byt_out(chan, rc);
	return rc;
}

/* socket is not bound to an interface. Send on ALL multicast capable interfaces */
static ssize_t chan_sendmsg_unbound(lc_channel_t *chan, struct msghdr *msg, int flags)
{
	int sock = chan->sock->sock;
	struct ifaddrs *ifap;
	ssize_t rc = 0;
	rc = getifaddrs(&ifap);
	if (rc == -1) return -1;
	for (struct ifaddrs *ifa = ifap; ifa; ifa = ifa->ifa_next) {
		if (ifa->ifa_addr == NULL) continue;
		if (ifa->ifa_addr->sa_family != AF_INET6) continue; /* IPv6 */
		if (!(ifa->ifa_flags & IFF_MULTICAST)) continue;    /* multicast */
		unsigned int ifx = if_nametoindex(ifa->ifa_name);
		rc = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifx, sizeof ifx);
		if (rc == -1) break;
		rc = chan_sendmsg(chan, msg, flags);
		if (rc == -1) break;
		/* process each interface only once */
		while (ifa->ifa_next && ifa->ifa_name == ifa->ifa_next->ifa_name) ifa = ifa->ifa_next;
	}
	freeifaddrs(ifap);
	return rc;
}

static ssize_t chan_sendmsg_tlv(lc_channel_t *chan, struct msghdr *msg, int flags)
{
	lc_tlv_t tlv_hash = { .type = LC_TLV_HASH, .len = HASHSIZE };
	const int hdrs = 2;
	struct iovec iov[msg->msg_iovlen + hdrs];
	const size_t iovlen = sizeof iov / sizeof iov[0];
	iov[0].iov_base = &tlv_hash;
	iov[0].iov_len = sizeof tlv_hash;
	iov[1].iov_base = chan->hash;
	iov[1].iov_len = HASHSIZE;
	for (int i = 0; i < (int)msg->msg_iovlen; i++) {
		iov[i + hdrs].iov_base = msg->msg_iov[i].iov_base;
		iov[i + hdrs].iov_len = msg->msg_iov[i].iov_len;
	}
	msg->msg_iov = iov;
	msg->msg_iovlen = iovlen;
	return chan_sendmsg(chan, msg, flags);
}

static ssize_t chan_prepare_sendmsg(lc_channel_t *chan, struct msghdr *msg, int flags)
{
	if (chan->bps_out) chan_ratelimit(chan);
	if (chan->sock->type == LC_SOCK_IN6) {
		msg->msg_name = (struct sockaddr *)&chan->sa;
		msg->msg_namelen = sizeof(struct sockaddr_in6);
	}
	else if (chan->sock->type == LC_SOCK_PAIR) {
		if (lc_socket_oil_cmp(chan->sock, chan->hash)) return 0;
		return chan_sendmsg_tlv(chan, msg, flags);
	}
	if (!chan->sock->ifx) return chan_sendmsg_unbound(chan, msg, flags);
	return chan_sendmsg(chan, msg, flags);
}

static ssize_t chan_send(lc_channel_t *chan, const void *buf, size_t len, int flags)
{
	struct iovec iov = { .iov_base = (void * const)buf, .iov_len = len};
	struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
	return chan_prepare_sendmsg(chan, &msg, flags);
}

#if HAVE_LIBSODIUM && HAVE_LIBLCRQ
static ssize_t channel_sendmsg_symm_encode(lc_channel_t *chan, struct msghdr *msg, const size_t len, int flags)
{
	const size_t elen = len + crypto_secretbox_MACBYTES + crypto_secretbox_NONCEBYTES;
	unsigned char ebuf[elen];
	unsigned char *nonce, *ptr;
	const int headers = (int)msg->msg_iovlen - 2;

	/* copy unencrypted data to buffer */
	ptr = ebuf;
	for (int i = headers; i < (int)msg->msg_iovlen; i++) {
		memcpy(ptr, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len);
		ptr += msg->msg_iov[i].iov_len;
	}

	/* generate nonce and encrypt in place */
	nonce = ebuf + len + crypto_secretbox_MACBYTES;
	randombytes_buf(nonce, crypto_secretbox_NONCEBYTES);
	crypto_secretbox_easy(ebuf, ebuf, len, nonce, chan->key.key);

	return chan_send(chan, ebuf, elen, flags);
}

static ssize_t channel_sendmsg_symm(lc_channel_t *chan, struct msghdr *msg, int flags)
{
	/* add up length of all msg parts excluding headers */
	const int headers = (int)msg->msg_iovlen - 2;
	size_t len = 0;
	for (int i = headers; i < (int)msg->msg_iovlen; i++) {
		len += msg->msg_iov[i].iov_len;
	}
	return channel_sendmsg_symm_encode(chan, msg, len, flags);
}
#endif

#ifdef HAVE_LIBLCRQ
static ssize_t channel_send_coded_rq(lc_channel_t *chan, const void *buf, size_t len, int flags)
{
	uint32_t esi = 0;
	uint8_t sbn = 0;
	rq_pid_t pid = 0;
#ifndef HAVE_LIBSODIUM
	if (chan->coding & LC_CODE_SYMM) return (errno = ENOTSUP), -1;
#endif
	if (buf) {
		/* new data, encode */
		chan->pid = pid;
		if (!chan->rq) chan->rq = rq_init(len, chan->T);
		if (!chan->rq) return -1;
		if (rq_encode(chan->rq, (uint8_t *)buf, len)) return -1;
	}
	else {
		pid = chan->pid;
		sbn = rq_pid2sbn(pid);
		esi = rq_pid2esi(pid);
		if (!chan->rq) return -1; /* no rq object */
		if ((uint32_t)(rq_KP(chan->rq) + RQ_OVERHEAD * 2) == ++esi) {
			esi = 0;
			if (rq_Z(chan->rq) == ++sbn) sbn = 0;
		}
		pid = rq_pidset(sbn, esi);
		chan->pid = pid;
	}
	/* send next symbol */
	struct iovec iov[6];
	struct msghdr msg = { .msg_iov = iov, };
	uint8_t sym[chan->T];
	const int rqf = ((chan->coding & LC_CODE_FEC_RAND) || esi >= rq_K(chan->rq)) ? RQ_RAND : 0;
	int v = 0;
	lc_tlv_t tlv_hash = { .type = LC_TLV_HASH, .len = HASHSIZE };
	if (chan->sock->type != LC_SOCK_IN6) {
		iov[v].iov_base = &tlv_hash;
		iov[v++].iov_len = sizeof tlv_hash;
		iov[v].iov_base = chan->hash;
		iov[v++].iov_len = HASHSIZE;
	}
#if HAVE_RQ_OTI
	rq_oti_t oti = 0;
	rq_scheme_t scheme = 0;
	if (chan->coding & LC_CODE_FEC_OTI) {
		rq_oti(chan->rq, &oti, &scheme);
		iov[v].iov_base = &oti;
		iov[v++].iov_len = sizeof oti;
		iov[v].iov_base = &scheme;
		iov[v++].iov_len = sizeof scheme;
	}
#endif
	iov[v].iov_base = &pid;
	iov[v++].iov_len = sizeof pid;
	iov[v].iov_base = sym;
	iov[v++].iov_len = sizeof sym;
	msg.msg_iovlen = v;
	rq_symbol(chan->rq, &pid, sym, rqf);
#ifdef HAVE_LIBSODIUM
	if (chan->coding & LC_CODE_SYMM) return channel_sendmsg_symm(chan, &msg, flags);
#endif
	return chan_prepare_sendmsg(chan, &msg, flags);
}
#endif

#ifdef HAVE_LIBSODIUM
static ssize_t channel_send_symm(lc_channel_t *chan, const void *buf, size_t len, int flags)
{
	const size_t elen = len + crypto_secretbox_MACBYTES + crypto_secretbox_NONCEBYTES;
	unsigned char ebuf[elen];
	unsigned char *sbuf = (unsigned char *)buf;

	memset(ebuf, 0, elen);
	if (buf && (chan->coding & LC_CODE_SYMM)) {
		unsigned char *nonce = ebuf + len + crypto_secretbox_MACBYTES;
		randombytes_buf(nonce, crypto_secretbox_NONCEBYTES);
		crypto_secretbox_easy(ebuf, sbuf, len, nonce, chan->key.key);
		sbuf = ebuf;
	}
	return chan_send(chan, sbuf, elen, flags);
}
#endif

ssize_t lc_channel_send(lc_channel_t *chan, const void *buf, size_t len, int flags)
{
	if (chan->coding & LC_CODE_FEC_RQ) {
#ifdef HAVE_LIBLCRQ
		return channel_send_coded_rq(chan, buf, len, flags);
#else
		return (errno = ENOTSUP), -1;
#endif
	}
	if (chan->coding & LC_CODE_SYMM) {
#ifdef HAVE_LIBSODIUM
		return channel_send_symm(chan, buf, len, flags);
#else
		return (errno = ENOTSUP), -1;
#endif
	}
	return chan_send(chan, buf, len, flags);
}

/* gather up msg parts into a single buffer for encoding */
static ssize_t channel_send_gather(lc_channel_t *chan, struct msghdr *msg, const size_t len, int flags)
{
	char buf[len];
	char *ptr = buf;
	for (int i = 0; i < (int)msg->msg_iovlen; i++) {
		memcpy(ptr, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len);
		ptr += msg->msg_iov[i].iov_len;
	}
	return lc_channel_send(chan, buf, len, flags);
}

static ssize_t channel_send_encoded(lc_channel_t *chan, struct msghdr *msg, int flags)
{
	size_t len = 0;
	for (int i = 0; i < (int)msg->msg_iovlen; i++) {
		len += msg->msg_iov[i].iov_len;
	}
	return channel_send_gather(chan, msg, len, flags);
}

ssize_t lc_channel_sendmsg(lc_channel_t *chan, struct msghdr *msg, int flags)
{
	if (chan->coding) return channel_send_encoded(chan, msg, flags);
	return chan_prepare_sendmsg(chan, msg, flags);
}

ssize_t lc_socket_sendmsg(lc_socket_t *sock, struct msghdr *msg, int flags)
{
	ssize_t bytes = 0, rc = -1;
	if (!pthread_mutex_lock(&sock->ctx->mtx)) {
		for (lc_channel_t *chan = sock->ctx->chan_list; chan; chan = chan->next) {
			pthread_mutex_unlock(&sock->ctx->mtx);
			if (chan->sock == sock) {
				if ((rc = lc_channel_sendmsg(chan, msg, flags)) > 0) {
					bytes += rc;
				}
				else return -1;
			}
			if (pthread_mutex_lock(&sock->ctx->mtx)) return -1;
		}
		pthread_mutex_unlock(&sock->ctx->mtx);
	}
	return (rc > 0) ? bytes : -1;
}

ssize_t lc_socket_send(lc_socket_t *sock, const void *buf, size_t len, int flags)
{
	struct iovec iov = { .iov_base = (void * const)buf, .iov_len = len};
	struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
	return lc_socket_sendmsg(sock, &msg, flags);
}

int lc_channel_nack_handler_thr(lc_channel_t *chan, int n_seconds, void *(*nack_thread)(void *))
{
	pthread_attr_t attr;
	pthread_mutexattr_t mutexattr;
	int saveerrno;
	lc_channel_logging_t *logging = calloc(1, sizeof(lc_channel_logging_t));
	if (! logging) return -1;
	logging->chan_nack = lc_channel_sidehash(chan, (void *)SIDE_CHANNEL_NACK, strlen(SIDE_CHANNEL_NACK));
	if (! logging->chan_nack) goto error;
	logging->sock_nack = lc_socket_new(chan->ctx);
	if (! logging->sock_nack) goto error;
	lc_channel_bind(logging->sock_nack, logging->chan_nack);
	if (lc_channel_join(logging->chan_nack)) goto error;
	logging->chan_data = chan;
	logging->n_seconds = n_seconds;
	if (chan->logging) lc_close_nack_logger(chan->logging);
	chan->logging = logging;
	pthread_mutexattr_init(&mutexattr);
	pthread_mutex_init(&logging->mutex, &mutexattr);
	pthread_mutexattr_destroy(&mutexattr);
	logging->oldest = NULL;
	logging->newest = NULL;
	pthread_attr_init(&attr);
	pthread_create(&logging->nack_thread, &attr, nack_thread, logging);
	pthread_attr_destroy(&attr);
	return 0;
error:
	saveerrno = errno;
	if (logging->chan_nack) lc_channel_free(logging->chan_nack);
	if (logging->sock_nack) lc_socket_close(logging->sock_nack);
	free(logging);
	errno = saveerrno;
	return -1;
}

static void *nack_thread(void * _logging)
{
	lc_channel_logging_t * logging = _logging;
	pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
	while (1) {
		/* wait for a NACK request for up to a second; if one arrives, process it;
		 * also go through the list of logged messages to see if anything needs
		 * to be removed */
		struct pollfd pfd = {
			.fd = logging->sock_nack->sock,
			.events = POLLIN,
		};
		lc_packet_log_t *oldest = aload(&logging->oldest);
		lc_packet_log_t *newest;
		int ok;
		ok = poll(&pfd, 1, 1000);
		if (ok > 0) {
			pthread_mutex_lock(&logging->mutex);
			/* process this request */
			uint64_t nack;
			lc_seq_t seq;
			if (lc_socket_recv(logging->sock_nack, &nack, sizeof(nack), 0) == -1) break;
			seq = be64toh(nack);
			/* see if we have this to resend */
			oldest = aload(&logging->oldest);
			newest = aload(&logging->newest);
			if (oldest && oldest->seq <= seq && seq <= newest->seq) {
				/* the message may be in the list, and we exit the critical section
				 * now as the only thing other thread may do is to add entries after
				 * logging->newest and we are going to make sure we don't look there;
				 * this improves performance for the other threads if the list gets
				 * very long */
				lc_packet_log_t * end = newest, * msg = NULL;
				/* find this message in the list */
				msg = oldest;
				while (msg && msg->seq < seq && msg != end) msg = msg->next;
				/* MUST unlock before lc_channel_send() */
				pthread_mutex_unlock(&logging->mutex);
				/* if found, resend it (no point checking return
				 * of lc_channel_send() here, as nowhere to send
				 * error. Just continue. */
				if (msg && msg->seq == seq)
					lc_channel_send(logging->chan_data, msg->data, msg->len, 0);
			}
			else pthread_mutex_unlock(&logging->mutex);
		}
		pthread_mutex_lock(&logging->mutex);
		oldest = aload(&logging->oldest);
		if (oldest) {
			time_t expire;
			expire = time(NULL) - logging->n_seconds;
			while (oldest && oldest->time < expire) {
				lc_packet_log_t * go = oldest;
				oldest = aload(&go->next);
				if (!oldest) astor(&logging->newest, NULL);
				free(go);
				oldest = aload(&logging->oldest);
			}
		}
		pthread_mutex_unlock(&logging->mutex);
	}
	return NULL;
}

int lc_channel_nack_handler(lc_channel_t *chan, int n_seconds)
{
	return lc_channel_nack_handler_thr(chan, n_seconds, nack_thread);
}

static int lc_channel_logmsg(lc_channel_logging_t *logging, const void *buf, size_t len, lc_seq_t seq)
{
	lc_packet_log_t * msg;
	msg = calloc(1, sizeof(lc_packet_log_t) + len);
	if (! msg) return -1;
	msg->next =  NULL;
	msg->len = len;
	msg->time = time(NULL);
	msg->seq = seq;
	memcpy(msg->data, buf, len);
	pthread_mutex_lock(&logging->mutex);
	if (logging->newest)
		logging->newest->next = msg;
	else
		astor(&logging->oldest, msg);
	astor(&logging->newest, msg);
	pthread_mutex_unlock(&logging->mutex);
	return 0;
}

int lc_channel_nack_add_log(lc_channel_t *chan, const void *buf, size_t len, lc_seq_t seq)
{
	if (chan && chan->logging)
		return lc_channel_logmsg(chan->logging, buf, len, seq);
	else return 0;
}

ssize_t lc_msg_sendto(int sock, const void *buf, size_t len, struct sockaddr_in6 *sa, int flags)
{
	if (flags & MSG_DROP) return len;
	return sendto(sock, buf, len, flags, (struct sockaddr *)sa, sizeof(struct sockaddr_in6));
}

ssize_t lc_msg_send(lc_channel_t *chan, lc_message_t *msg)
{
	lc_message_head_t *head = NULL;
	char *buf = NULL;
	size_t len = 0;
	size_t ohead = 0;
	ssize_t bytes = 0;
	struct timespec t = {0};
	int state = 0;
	int err = 0;

	if (!chan->sock) return LC_ERROR_SOCKET_REQUIRED;
	if (msg->len > 0 && !msg->data) return LC_ERROR_MESSAGE_EMPTY;

	pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &state);

	head = calloc(1, sizeof(lc_message_head_t));
	if (!head) return LC_ERROR_MALLOC;

	if (msg->timestamp)
		head->timestamp = htobe64(msg->timestamp);
	else if (!clock_gettime(CLOCK_REALTIME, &t))
		head->timestamp = htobe64(t.tv_sec * 1000000000 + t.tv_nsec);

	head->seq = htobe64(add_fetch(&chan->seq, 1));
	lc_getrandom(&head->rnd, sizeof(lc_rnd_t));
	head->len = htobe64(msg->len);
	head->op = msg->op;
	len = msg->len;
	if (chan->coding & LC_CODE_SYMM) {
#ifdef HAVE_LIBSODIUM
		ohead = crypto_secretbox_MACBYTES + crypto_secretbox_NONCEBYTES;
#else
		free(head);
		return (errno = ENOTSUP), -1;
#endif
	}
	buf = calloc(1, sizeof(lc_message_head_t) + len + ohead);
	if (!buf) {
		free(head);
		return LC_ERROR_MALLOC;
	}
	memcpy(buf, head, sizeof(lc_message_head_t));
	memcpy(buf + sizeof(lc_message_head_t), msg->data, len);
	len += sizeof(lc_message_head_t);

	if (chan->coding & LC_CODE_SYMM) {
#ifdef HAVE_LIBSODIUM
		unsigned char *nonce;
		nonce = (unsigned char *)buf + len + crypto_secretbox_MACBYTES;
		randombytes_buf(nonce, crypto_secretbox_MACBYTES);
		crypto_secretbox_easy((unsigned char *)buf, (unsigned char *)buf,
				len, nonce, chan->key.key);
#endif
	}

	if (chan->logging)
		/* chan->seq could have been changed by another thread in the meantime,
		 * so use head->seq which we know hasn't */
		lc_channel_logmsg(chan->logging, buf, len + ohead, be64toh(head->seq));
	bytes = chan_send(chan, buf, len + ohead, 0);
	if (bytes == -1) err = errno;

	free(head);
	free(buf);
	pthread_setcancelstate(state, NULL);

	if (err) errno = err;
	return bytes;
}

#ifndef IPV6_MULTICAST_ALL
static int lc_socket_group_joined(lc_socket_t *sock, struct in6_addr *grp)
{
	for (lc_grplist_t *g = sock->grps; g; g = g->next) {
		if (!memcmp(&g->grp, grp, sizeof(struct in6_addr))) return 1;
	}
	return 0;
}
#endif

lc_channel_t *lc_channel_by_address(lc_ctx_t *lctx, struct in6_addr *addr)
{
	lc_channel_t *p = NULL;
	if (!pthread_mutex_lock(&lctx->mtx)) {
		for (p = lctx->chan_list; p; p = p->next) {
			if (!memcmp(addr,& p->sa.sin6_addr, sizeof(struct in6_addr)))
				break;
		}
		pthread_mutex_unlock(&lctx->mtx);
	}
	if (!p) errno = ENOENT;
	return p;
}

lc_channel_t *lc_channel_by_hash(lc_ctx_t *lctx, unsigned char *hash, size_t hashlen)
{
	lc_channel_t *p = NULL;
	if (!pthread_mutex_lock(&lctx->mtx)) {
		for (p = lctx->chan_list; p; p = p->next) {
			if (!memcmp(p->hash, hash, hashlen)) break;
		}
		pthread_mutex_unlock(&lctx->mtx);
	}
	if (!p) errno = ENOENT;
	return p;
}

/* return channel which matches both the socket and hash */
lc_channel_t *lc_channel_socket_by_hash(lc_socket_t *sock, unsigned char *hash, size_t hashlen)
{
	lc_ctx_t *lctx = sock->ctx;
	lc_channel_t *p = NULL;
	if (!pthread_mutex_lock(&lctx->mtx)) {
		for (p = lctx->chan_list; p; p = p->next) {
			if (p->sock != sock) continue;
			if (!memcmp(p->hash, hash, hashlen)) break;
		}
		pthread_mutex_unlock(&lctx->mtx);
	}
	if (!p) errno = ENOENT;
	return p;
}

#ifdef HAVE_LIBSODIUM
static ssize_t lc_channel_decoded(lc_channel_t *chan, unsigned char *m, unsigned char *c, size_t bytes)
{
	size_t clen = bytes - crypto_secretbox_NONCEBYTES;
	size_t mlen = clen - crypto_secretbox_MACBYTES;
	unsigned char *nonce = c + clen;
	if (bytes < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES + 1)
		return (errno = EMSGSIZE), -1;
	if (crypto_secretbox_open_easy(m, c, clen, nonce, chan->key.key))
		return (errno = EBADMSG), -1;
	return mlen;
}
#endif

ssize_t lc_msg_recv(lc_socket_t *sock, lc_message_t *msg)
{
	ssize_t zi = 0;
	struct iovec iov[1];
	struct msghdr msgh = {0};
	struct in6_pktinfo pi = {0};
	struct sockaddr_in6 from;
	unsigned char buf[BUFSIZ];
	struct cmsghdr *cmsg;
	char ctl[CMSG_SPACE(sizeof(struct in6_pktinfo))];
	lc_message_head_t head;
	lc_channel_t *chan = NULL;
	int opt = 1;

#ifndef IPV6_MULTICAST_ALL
recv_again:
#endif
	iov[0].iov_base = buf;
	iov[0].iov_len = sizeof buf;
	msgh.msg_control = ctl;
	msgh.msg_controllen = sizeof ctl;
	msgh.msg_name = &from;
	msgh.msg_namelen = sizeof from;
	msgh.msg_iov = iov;
	msgh.msg_iovlen = 1;
	if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opt, sizeof opt) == -1)
		return -1;
	pthread_testcancel();
	if ((zi = recvmsg(sock->sock, &msgh, 0)) <= 0) return zi;
	for (cmsg = CMSG_FIRSTHDR(&msgh); cmsg; cmsg = CMSG_NXTHDR(&msgh, cmsg)) {
		if (cmsg->cmsg_type == IPV6_PKTINFO) {
			/* may not be aligned, copy */
			memcpy(&pi, CMSG_DATA(cmsg), sizeof(struct in6_pktinfo));
			/* if we have a channel, we can use it for gap detection */
			chan = lc_channel_by_address(sock->ctx, &pi.ipi6_addr);
#ifndef IPV6_MULTICAST_ALL
			/* destination is group we haven't joined - drop it */
			if (!lc_socket_group_joined(sock, &pi.ipi6_addr)) goto recv_again;
#endif
			break;
		}
	}
	if (sock->ccoding & LC_CODE_SYMM) {
#ifdef HAVE_LIBSODIUM
		chan = lc_channel_by_address(sock->ctx, &pi.ipi6_addr);
		if (!chan) return -1;
		zi = lc_channel_decoded(chan, buf, buf, zi);
#else
		return (errno = ENOTSUP), -1;
#endif
	}
	if (lc_msg_init_size(msg, (size_t)zi - sizeof(lc_message_head_t)) == -1)
		return -1;
	memcpy(&head, buf, sizeof(lc_message_head_t));
	msg->dst = pi.ipi6_addr;
	msg->src = (&from)->sin6_addr;
	msg->seq = be64toh(head.seq);
	msg->rnd = be64toh(head.rnd);
	msg->timestamp = be64toh(head.timestamp);
	msg->op = head.op;
	if (chan && chan->gap_detect) do_gap_detection(chan->gap_detect, msg->seq);
	if (zi > (ssize_t)sizeof(lc_message_head_t)) {
		memcpy(msg->data, buf + sizeof(lc_message_head_t), msg->len);
	}
	return zi;
}

int lc_socket_listen_cancel(lc_socket_t *sock)
{
	if (sock->thread) {
		if (pthread_cancel(sock->thread))
			return LC_ERROR_THREAD_CANCEL;
		if (pthread_join(sock->thread, NULL))
			return LC_ERROR_THREAD_JOIN;
		sock->thread = 0;
	}
	return 0;
}

static void lc_op_pong_handler(lc_socket_call_t *sc, lc_message_t *msg)
{
	if (sc->callback_msg) sc->callback_msg(msg);
}

static void lc_op_ping_handler(lc_socket_call_t *sc, lc_message_t *msg)
{
	(void) sc; /* unused */
	int opt = LC_OP_PONG;

	/* received PING, echo PONG back to same channel */
	lc_msg_set(msg, LC_ATTR_OPCODE, &opt);
	lc_msg_send(msg->chan, msg);
}

static void lc_op_data_handler(lc_socket_call_t *sc, lc_message_t *msg)
{
	/* callback to message handler */
	if (sc->callback_msg) sc->callback_msg(msg);
}

static ssize_t lc_socket_recvmsg_if(lc_socket_t *sock, struct msghdr *msg, int flags,
		struct in6_pktinfo *pi)
{
	char ctl[CMSG_SPACE(sizeof(struct in6_pktinfo))];
	struct cmsghdr *cmsg;
	ssize_t bytes;
	int opt = 1;

	/* We're only interested in packets arriving on the socket->ifx
	 * interface. If we bind to an interface-specific address, we will get no
	 * multicast packets. If bound to either INADDR_ANY or the multicast
	 * group address, we receive packets on all interfaces. So, we need to
	 * filter by extracting the receiving interface from ancillary data */

	if (sock->type == LC_SOCK_IN6) {
		if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &opt, sizeof opt) == -1)
			return -1;

		/* provide control buffer if caller hasn't */
		if (!msg->msg_control) {
			msg->msg_control = ctl;
			msg->msg_controllen = sizeof ctl;
		}
	}

	for (;;) {
		bytes = recvmsg(sock->sock, msg, flags);
		if (sock->type != LC_SOCK_IN6) return bytes;
		for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
			if (cmsg->cmsg_type == IPV6_PKTINFO) {
				memcpy(pi, CMSG_DATA(cmsg), sizeof(struct in6_pktinfo));
#ifndef IPV6_MULTICAST_ALL
				if (!lc_socket_group_joined(sock, &pi->ipi6_addr)) {
					if (flags & MSG_PEEK) {
						/* read & drop packet */
						int beijing = flags^MSG_PEEK;
						recvmsg(sock->sock, msg, beijing);
					}
				}
				else
#endif
				if (!sock->ifx || sock->ifx == pi->ipi6_ifindex) return bytes;
				else break;
			}
		}
	}
	/* not reached */
}

#ifdef HAVE_LIBSODIUM
static ssize_t channel_msg_decrypt(lc_channel_t *chan, struct msghdr *msg,
		unsigned char *buf, size_t len)
{
	unsigned char *ptr;
	const size_t clen = len - crypto_secretbox_NONCEBYTES;
	unsigned char *nonce = buf + clen;
	if (len < crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES + 1)
		return (errno = EMSGSIZE), -1;
	if (crypto_secretbox_open_easy(buf, buf, clen, nonce, chan->key.key))
		return (errno = EBADMSG), -1;
	/* scatter parts */
	ptr = buf;
	for (int v = 0; v < (int)msg->msg_iovlen; v++) {
		memcpy(msg->msg_iov[v].iov_base, ptr, msg->msg_iov[v].iov_len);
		ptr += msg->msg_iov[v].iov_len;
	}
	return len - crypto_secretbox_NONCEBYTES - crypto_secretbox_MACBYTES;
}

/* recv data prior to encoding */
static ssize_t socket_recvmsg_data(lc_socket_t *sock, struct msghdr *msg, size_t len, int flags)
{
	/* provide a buffer large enough to recv encrypted data */
	unsigned char buf[len + crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES];
	char ctl[CMSG_SPACE(sizeof(struct in6_pktinfo))];
	struct in6_pktinfo pi = {0};
	struct msghdr emsg = {0};
	const int headers = (sock->type == LC_SOCK_IN6) ? 0 : 1;
	struct iovec iov[2 + headers];
	lc_tlv_hash_t tlv_hash;
	lc_channel_t *chan;
	ssize_t rc;
	ssize_t required = crypto_secretbox_NONCEBYTES + crypto_secretbox_MACBYTES + 1;
	int v = 0;
	if (sock->type == LC_SOCK_IN6) {
		emsg.msg_control = ctl;
		emsg.msg_controllen = sizeof ctl;
	}
	else {
		iov[v].iov_base = &tlv_hash;
		iov[v++].iov_len = sizeof tlv_hash;
		required += sizeof tlv_hash;
	}
	iov[v].iov_len = sizeof buf;
	iov[v++].iov_base = buf;
	emsg.msg_iov = iov;
	emsg.msg_iovlen = v;
	rc = lc_socket_recvmsg_if(sock, &emsg, flags, &pi);
	if (rc == -1) return -1;
	if (rc < required) return (errno = ENOMSG), -1;
	if (sock->type == LC_SOCK_IN6) {
		chan = lc_channel_by_address(sock->ctx, &pi.ipi6_addr);
	}
	else {
		chan = lc_channel_socket_by_hash(sock, tlv_hash.value, HASHSIZE);
		rc -= sizeof tlv_hash;
	}
	if (chan == NULL) return -1;
	if (chan->coding & LC_CODE_SYMM) {
		return channel_msg_decrypt(chan, msg, buf, rc);
	}
	return rc;
}

static ssize_t lc_socket_recvmsg_encoded(lc_socket_t *sock, struct msghdr *msg, int flags)
{
	size_t len = 0;
	for (int i = 0; i < (int)msg->msg_iovlen; i++) {
		len += msg->msg_iov[i].iov_len;
	}
	return socket_recvmsg_data(sock, msg, len, flags);
}
#endif

#if !(HAVE_RECVMMSG)
/* wrapper for recvmsg for systems with no recvmmsg() - timeout ignored */
static int recvmmsg(int sockfd, struct mmsghdr *msgvec, unsigned int vlen,
		int flags, struct timespec *timeout)
{
	(void)timeout; /* ignored */
	ssize_t rc;
	int msgs = 0;
	int waitfor = 0;
	if (flags & MSG_WAITFORONE) {
		flags &= ~MSG_WAITFORONE;
		waitfor = 1;
	}
	for (unsigned int u = 0; u < vlen; u++) {
		rc = recvmsg(sockfd, &msgvec[u].msg_hdr, flags);
		if (rc == -1) {
			if (msgs > 0) break;
			else return -1;
		}
		if (waitfor) {
			flags |= MSG_DONTWAIT;
			waitfor ^= waitfor;
		}
		msgvec[u].msg_len = rc;
		msgs++;
	}
	return msgs;
}
#endif

#ifdef HAVE_LIBSODIUM
static int lc_socket_recvmmsg_coded(lc_socket_t *sock, struct mmsghdr *msgvec, unsigned int vlen,
		int flags, struct timespec *timeout)
{
	(void)timeout; /* ignored */
	ssize_t rc;
	int msgs = 0;
	int waitfor = 0;
	if (flags & MSG_WAITFORONE) {
		flags &= ~MSG_WAITFORONE;
		waitfor = 1;
	}
	for (unsigned int u = 0; u < vlen; u++) {
		rc = lc_socket_recvmsg_encoded(sock, &msgvec[u].msg_hdr, flags);
		if (rc == -1) {
			if (msgs > 0) break;
			else return -1;
		}
		if (waitfor) {
			flags |= MSG_DONTWAIT;
			waitfor ^= waitfor;
		}
		msgvec[u].msg_len = rc;
		msgs++;
	}
	return msgs;
}
#endif

int lc_socket_recvmmsg(lc_socket_t *sock, struct mmsghdr *msgvec, unsigned int vlen,
		int flags, struct timespec *timeout)
{
	if (sock->ccoding & LC_CODE_SYMM) {
#ifdef HAVE_LIBSODIUM
		return lc_socket_recvmmsg_coded(sock, msgvec, vlen, flags, timeout);
#else
		return (errno = ENOTSUP), -1;
#endif
	}
	return recvmmsg(sock->sock, msgvec, vlen, flags, timeout);
}

ssize_t lc_socket_recvmsg(lc_socket_t *sock, struct msghdr *msg, int flags)
{
	if (sock->ccoding & LC_CODE_SYMM) {
#ifdef HAVE_LIBSODIUM
		return lc_socket_recvmsg_encoded(sock, msg, flags);
#else
		return (errno = ENOTSUP), -1;
#endif
	}
	if (sock->ifx) {
		char ctl[CMSG_SPACE(sizeof(struct in6_pktinfo))];
		struct in6_pktinfo pi = {0};
		msg->msg_control = ctl;
		msg->msg_controllen = sizeof ctl;
		return lc_socket_recvmsg_if(sock, msg, flags, &pi);
	}
	return recvmsg(sock->sock, msg, flags);
}

ssize_t lc_socket_recv(lc_socket_t *sock, void *buf, size_t len, int flags)
{
	struct iovec iov = { .iov_base = buf, .iov_len = len };
	struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
	return lc_socket_recvmsg(sock, &msg, flags);
}

#ifdef HAVE_LIBLCRQ
inline static ssize_t lc_channel_recv_lcrq(lc_channel_t *chan, void *buf, size_t len, int flags)
{
	const uint64_t F = rq_F(chan->rq);
	const uint16_t OH = RQ_OVERHEAD;
	const uint16_t KP = rq_KP(chan->rq);
	const uint16_t K = rq_K(chan->rq);
	const uint16_t Z = rq_Z(chan->rq);
	const uint16_t T = rq_T(chan->rq);
	const uint16_t nesi = KP + OH;
	const uint32_t M = nesi * (uint32_t)Z;
	uint32_t ESI[Z][nesi];
	uint8_t *blk;
	uint8_t sym[T];
	uint8_t sbn;
	uint32_t esi;
	uint16_t orig = K;
	uint16_t rec = 0;
	struct iovec iov[5];
	struct msghdr msg = {
		.msg_iov = iov,
	};
	time_t start_time;
	int rec_diff;
	ssize_t zi = 0, rc;
	rq_pid_t pid = 0;
	int v = 0;
	int idx;

	if ((size_t)F > len) return (errno = ERANGE), -1;

	blk = calloc(Z, (uint32_t)nesi * T);
	if (!blk) return -1;
	pthread_cleanup_push(free, blk);
#if HAVE_RQ_OTI
	rq_oti_t oti = 0;
	rq_scheme_t scheme = 0;
	if (chan->coding & LC_CODE_FEC_OTI) {
		iov[v].iov_base = &oti;
		iov[v++].iov_len = sizeof oti;
		iov[v].iov_base = &scheme;
		iov[v++].iov_len = sizeof scheme;
	}
#endif
	iov[v].iov_base = &pid;
	iov[v++].iov_len = sizeof pid;
	iov[v].iov_base = sym;
	iov[v++].iov_len = sizeof sym;
	msg.msg_iovlen = v;
	memset(ESI, ~0, sizeof ESI);

	/* store the received symbol:
	 * - if ESI < K, store at blk[sbn][esi]
	 * - if ESI >= K, store in next available repair slot >= K
	 * - if ESI >= K and all slots >= K used, backfill K
	 * - update bitmap, ESI list
	 * - if received symbols >= KP + OH, decode and return */
	start_time = time(NULL);
	rec_diff = 0;
	while (orig && rec < M) {
		/* data seems to stop arriving in the middle, with no new
		 * MLD listener reports being sent, so that the sender does
		 * not know we are still waiting; so have a timeout on each
		 * packet calculated as:
		 * 30 seconds on the initial packet
		 * 10 seconds within a second of the start
		 * 10 times the average packet interval after that
		 * this means that the timeout will self-adjust to the
		 * rate things are arriving; the caller will need to retry
		 * this, so we return 0 with an errno of ETIMEDOUT and they
		 * can check for that */
		struct pollfd pfd;
		int timeout, nready;
recvnext:
		pfd.fd = chan->sock->sock;
		pfd.events = POLLIN;
		if (rec <= rec_diff) {
			timeout = 30000;
		} else {
			time_t now = time(NULL), diff = now - start_time;
			if (diff < 2)
				timeout = 10000;
			else
				timeout = diff * 10000 / (rec - rec_diff);
			rec_diff = rec;
			start_time = now;
		}
		if ((nready = poll(&pfd, 1, timeout)) < 1) {
			if (nready < 0) {
				if (errno == EINTR) continue;
				goto err_free_blk;
			}
			/* timed out, try a part and rejoin */
			if (lc_channel_part(chan) == -1) goto err_free_blk;
			if (lc_channel_join(chan) == -1) goto err_free_blk;
			continue;
		}
		rc = lc_socket_recvmsg(chan->sock, &msg, flags);
		if (rc == -1) { zi = -1; goto err_free_blk; }
#if HAVE_RQ_OTI
		if (chan->coding & LC_CODE_FEC_OTI) {
			if (rq_oti_F(oti) != F || rq_oti_T(oti) != T) continue;
		}
#endif
		zi += rc;
		sbn = rq_pid2sbn(pid);
		esi = rq_pid2esi(pid);
		if (sbn >= Z) continue;
		if (esi < K) {
			if (ESI[sbn][esi] == esi) continue;
			idx = esi;
			orig--;
		}
		else for (idx = nesi - 1; idx; idx--) {
			if (ESI[sbn][idx] == esi) goto recvnext;
			if (ESI[sbn][idx] == UINT32_MAX) break;
		}
		if (ESI[sbn][idx] == UINT32_MAX) rec++;
		ESI[sbn][idx] = esi;
		memcpy(&blk[(sbn * nesi + idx) * T], sym, T);
	}

	/* decode blocks */
	size_t blocksz = (uint32_t)T * K, sz = len;
	uint8_t *dec = buf, *enc = blk;
	for (uint8_t sbn = 0; sbn < Z; sbn++) {
		if (!orig) memcpy(dec, enc, MIN(sz, blocksz)); /* original symbols, no decoding */
		else {
			if (sz < blocksz) {
				uint8_t * temp = malloc(blocksz);
				if (! temp) {
					zi = -1; break;
				}
				if (rq_decode(chan->rq, temp, enc, ESI[sbn], nesi) == -1) {
					zi = -1; break;
				}
				memcpy(dec, temp, sz);
				free(temp);
			} else {
				if (rq_decode(chan->rq, dec, enc, ESI[sbn], nesi) == -1) {
					zi = -1; break;
				}
			}
		}
		dec += blocksz; enc += blocksz; sz -= blocksz;
	}
err_free_blk:
	pthread_cleanup_pop(1); /* free(blk) */
	return zi;
}

/* peek at packet header to get OTI params */
#if HAVE_RQ_OTI
int lc_channel_oti_peek(lc_channel_t *chan, rq_oti_t *oti, rq_scheme_t *scheme)
{
	char buf[1280];
	ssize_t rc;
	size_t peekbyt = (chan->coding & LC_CODE_SYMM) ?
		sizeof buf : sizeof *oti + sizeof *scheme;
	rc = lc_socket_recv(chan->sock, buf, peekbyt, MSG_PEEK);
	if (rc >= (ssize_t)(sizeof *oti + sizeof *scheme)) {
		memcpy(oti, buf, sizeof(rq_oti_t));
		memcpy(scheme, buf + sizeof *oti, sizeof *scheme);
		if (rq_oti_T(*oti) != chan->T) return (errno = EBADMSG), -1;
		chan->rq = rq_init(rq_oti_F(*oti), chan->T);
	}
	return rc;
}
#endif /* HAVE_RQ_OTI */
#endif /* HAVE_LIBLCRQ */

static ssize_t lc_channel_recv_encoded(lc_channel_t *chan, void *buf, size_t len, int flags)
{
#ifdef HAVE_LIBLCRQ
	if (chan->coding & LC_CODE_FEC_RQ) {
		if (!chan->rq) chan->rq = rq_init(len, chan->T);
		if (!chan->rq) return -1;
		return lc_channel_recv_lcrq(chan, buf, len, flags);
	}
#endif
#ifdef HAVE_LIBSODIUM
	if (chan->coding & LC_CODE_SYMM) {
		struct iovec iov = { .iov_base = buf, .iov_len = len };
		struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1 };
		return socket_recvmsg_data(chan->sock, &msg, len, flags);
	}
#endif
	return (errno = ENOTSUP), -1;
}

ssize_t lc_channel_recv(lc_channel_t *chan, void *buf, size_t len, int flags)
{
	if (!chan || !buf || !len) return (errno = EINVAL), -1;
	if (chan->coding)
		return lc_channel_recv_encoded(chan, buf, len, flags);
	return lc_socket_recv(chan->sock, buf, len, flags);

}

static ssize_t channel_recvmsg_encoded(lc_channel_t *chan, struct msghdr *msg, size_t len, int flags)
{
	char buf[len];
	char *ptr = buf;
	ssize_t rc;
	if ((rc = lc_channel_recv(chan, buf, len, flags)) == -1) return -1;
	/* scatter parts */
	for (int v = 0; v < (int)msg->msg_iovlen; v++) {
		memcpy(msg->msg_iov[v].iov_base, ptr, msg->msg_iov[v].iov_len);
		ptr += msg->msg_iov[v].iov_len;
	}
	return rc;
}

ssize_t lc_channel_recvmsg(lc_channel_t *chan, struct msghdr *msg, int flags)
{
	if (chan->coding) {
		size_t len = 0;
		for (int i = 0; i < (int)msg->msg_iovlen; i++) {
			len += msg->msg_iov[i].iov_len;
		}
		return channel_recvmsg_encoded(chan, msg, len, flags);
	}
	return lc_socket_recvmsg(chan->sock, msg, flags);
}

static void process_msg(lc_socket_call_t *sc, lc_message_t *msg)
{
	lc_channel_t *chan;

	inet_ntop(AF_INET6, &msg->dst, msg->dstaddr, INET6_ADDRSTRLEN);
	inet_ntop(AF_INET6, &msg->src, msg->srcaddr, INET6_ADDRSTRLEN);

	/* update channel stats */
	chan = lc_channel_by_address(sc->sock->ctx, &msg->dst);
	if (chan) {
		int (*logger)(lc_channel_t *, lc_message_t *, void *logdb) = aload(&lc_msg_logger);
		msg->chan = chan;
		lc_seq_t seq = aload(&chan->seq);
		astor(&chan->seq, (msg->seq > seq) ? msg->seq + 1 : seq + 1);
		chan->rnd = msg->rnd;
		if (logger) logger(chan, msg, NULL);
	}

	/* opcode handler */
	if (msg->op < LC_OP_MAX && lc_op_handler[msg->op])
		lc_op_handler[msg->op](sc, msg);

	/* callback to message handler */
	if (sc->callback_msg) sc->callback_msg(msg);
}

void *lc_socket_listen_thread(void *arg)
{
	ssize_t len;
	lc_message_t msg = {0};
	lc_socket_call_t *sc = arg;

	pthread_cleanup_push(free, arg);
	pthread_cleanup_push(lc_msg_free, &msg);
	while(1) {
		len = lc_msg_recv(sc->sock, &msg);
		if (len > 0) {
			msg.bytes = len;
			process_msg(sc, &msg);
		}
		if (len < 0) {
			lc_msg_free(&msg);
			if (sc->callback_err) sc->callback_err(len);
		}
		lc_msg_free(&msg);
	}
	/* not reached */
	pthread_cleanup_pop(0);
	pthread_cleanup_pop(0);

	return NULL;
}

int lc_socket_listen(lc_socket_t *sock, void (*callback_msg)(lc_message_t*),
					void (*callback_err)(int))
{
	pthread_attr_t attr = {0};
	lc_socket_call_t *sc;

	if (!sock) return LC_ERROR_SOCKET_REQUIRED;
	if (sock->thread) return LC_ERROR_SOCKET_LISTENING;

	sc = calloc(1, sizeof(lc_socket_call_t));
	if (!sc) return LC_ERROR_MALLOC;
	sc->sock = sock;
	sc->callback_msg = callback_msg;
	sc->callback_err = callback_err;

	pthread_attr_init(&attr);
	pthread_create(&sock->thread, &attr, &lc_socket_listen_thread, sc);
	pthread_attr_destroy(&attr);

	return 0;
}

static int lc_channel_membership_all(int sock, int opt, struct ipv6_mreq *req)
{
	struct ifaddrs *ifaddr, *ifa;
	int rc = (opt == IPV6_JOIN_GROUP) ? LC_ERROR_MCAST_JOIN : LC_ERROR_MCAST_PART;

	if (getifaddrs(&ifaddr) == -1) return -1;
	for (ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
		if (!(ifa->ifa_flags & IFF_MULTICAST)
		   || ifa->ifa_addr == NULL
		   || ifa->ifa_addr->sa_family != AF_INET6) continue;

		req->ipv6mr_interface = if_nametoindex(ifa->ifa_name);

		if (!setsockopt(sock, IPPROTO_IPV6, opt, req, sizeof(struct ipv6_mreq))) {
			rc = 0; /* report success if we joined anything */
		}
	}
	freeifaddrs(ifaddr);

	return rc;
}

int lc_socket_oil_cmp(lc_socket_t *sock, unsigned char *hash)
{
	unsigned char *oil = aload(&sock->oil);
	if (!oil || !hash) goto not_found;
	for (int h = 0; h < BLOOM_HASHES; h++) {
		if (!(oil[hash[h] % BLOOM_LEN])) goto not_found;
	}
	return 0;
not_found:
	return (errno = ENOENT), -1;
}

#ifndef IPV6_MULTICAST_ALL
static void lc_socket_group_add(lc_socket_t *sock, struct in6_addr *grp)
{
	lc_grplist_t *g, *newgrp;
	for (g = sock->grps; g; g = g->next) {
		if (!memcmp(&g->grp, grp, sizeof(struct in6_addr))) return;
		if (!g->next) break;
	}
	newgrp = calloc(1, sizeof(struct lc_grplist_s));
	newgrp->grp = *grp;
	if (!sock->grps) sock->grps = newgrp;
	else g->next = newgrp;
}

static void lc_socket_group_del(lc_socket_t *sock, struct in6_addr *grp)
{
	lc_grplist_t *prev = NULL;
	for (lc_grplist_t *g = sock->grps; g; g = g->next) {
		if (memcmp(g, grp, sizeof(struct in6_addr)) == 0) {
			if (prev) {
				prev->next = (g->next) ? g->next : NULL;
			}
			if (g == sock->grps) sock->grps = NULL;
			free(g);
		}
		prev = g;
	}
}
#endif

int lc_socket_oil_add(lc_socket_t *sock, unsigned char *hash)
{
	const unsigned max = 1 << (CHAR_BIT - 1);
	if (!sock->oil) {
		unsigned char *oil = malloc(BLOOM_LEN);
		if (!oil) return -1;
		memset(oil, 0, BLOOM_LEN);
		astor(&sock->oil, oil);
	}
	/* if hash not in filter, add it */
	if (lc_socket_oil_cmp(sock, hash)) {
		for (int h = 0; h < BLOOM_HASHES; h++) {
			unsigned char c = aload(&sock->oil[hash[h] % BLOOM_LEN]);
			while ((c < max) && !CAE(&sock->oil[hash[h] % BLOOM_LEN], &c, c + 1));
		}
	}
	return 0;
}

int lc_socket_oil_del(lc_socket_t *sock, unsigned char *hash)
{
	if (lc_socket_oil_cmp(sock, hash)) return -1;
	for (int h = 0; h < BLOOM_HASHES; h++) {
		if (sock->oil[hash[h] % BLOOM_LEN])
			sock->oil[hash[h] % BLOOM_LEN]--;
	}
	return 0;
}

static int lc_channel_membership_in6(lc_channel_t *chan, int opt, struct ipv6_mreq *req)
{
	int s = chan->sock->sock;
	if (opt == IPV6_JOIN_GROUP) {
#ifndef IPV6_MULTICAST_ALL
		lc_socket_group_add(chan->sock, &chan->sa.sin6_addr);
#endif
		lc_socket_oil_add(chan->sock, chan->hash);
	}
	else {
#ifndef IPV6_MULTICAST_ALL
		lc_socket_group_del(chan->sock, &chan->sa.sin6_addr);
#endif
		lc_socket_oil_del(chan->sock, chan->hash);
	}
	if (chan->sock->ifx) {
		req->ipv6mr_interface = chan->sock->ifx;
		return setsockopt(s, IPPROTO_IPV6, opt, req, sizeof(struct ipv6_mreq));
	}
	return lc_channel_membership_all(s, opt, req);
}

static int lc_channel_action_in6(lc_channel_t *chan, int opt)
{
	struct ipv6_mreq req = {0};
	if(!chan->sock) return LC_ERROR_SOCKET_REQUIRED;
	req.ipv6mr_multiaddr = chan->sa.sin6_addr;
	return lc_channel_membership_in6(chan, opt, &req);
}

static int lc_channel_action(lc_channel_t *chan, int opt)
{
	lc_socket_t *sock = chan->sock;
	if(!chan->sock) return LC_ERROR_SOCKET_REQUIRED;
	if (chan->sock->type == LC_SOCK_PAIR) sock = sock->pair;
	if (opt == IPV6_JOIN_GROUP) {
		lc_socket_oil_add(sock, chan->hash);
	}
	else {
		lc_socket_oil_del(sock, chan->hash);
	}
	switch (chan->sock->type) {
		case LC_SOCK_IN6:
			return lc_channel_action_in6(chan, opt);
		case LC_SOCK_PAIR:
			return 0;
		default:
			return (errno = ENOSYS), -1;
	}
}

int lc_channel_part(lc_channel_t *chan)
{
	return lc_channel_action(chan, IPV6_LEAVE_GROUP);
}

int lc_channel_join(lc_channel_t *chan)
{
	return lc_channel_action(chan, IPV6_JOIN_GROUP);
}

int lc_channel_unbind(lc_channel_t *chan)
{
	chan->sock->bound--;
	chan->sock = NULL;
	return 0;
}

static int lc_socket_bind_addr(lc_socket_t *sock, short port)
{
	int opt = 1;
	struct sockaddr_in6 any = {
		.sin6_family = AF_INET6,
		.sin6_addr = IN6ADDR_ANY_INIT,
		.sin6_port = port
	};

	if ((setsockopt(sock->sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) == -1)
		return LC_ERROR_SETSOCKOPT;

#ifdef SO_REUSEPORT
	if ((setsockopt(sock->sock, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt))) == -1)
		return LC_ERROR_SETSOCKOPT;
#endif

	if (bind(sock->sock, (struct sockaddr *)&any, sizeof(struct sockaddr_in6)) == -1) {
		/* ignore EINVAL "socket already bound" error */
		if (errno != EINVAL) return LC_ERROR_SOCKET_BIND;
	}

	return 0;
}

int lc_channel_bind(lc_socket_t *sock, lc_channel_t *chan)
{
	int rc = 0;

	/* Librecast sockets can have multiple channels bound to them, but we
	 * only need to call lc_socket_bind_addr() the first time */

	if (sock->type == LC_SOCK_IN6) {
		lc_channel_in6addr(chan); /* generate group address (IPv6 socket only) */
		rc = (sock->bound) ? 0 : lc_socket_bind_addr(sock, chan->sa.sin6_port);
	}

	if (!rc) {
		chan->sock = sock;
		sock->bound++;
		chan->sock->ccoding = chan->coding;
	}

	return rc;
}

static lc_channel_t * lc_channel_ins(lc_ctx_t *ctx, lc_channel_t *chan)
{
	chan->next = aload(&ctx->chan_list);
	chan->readers = 1;
	CAE_loop(&ctx->chan_list, &chan->next, chan);
	return chan;
}

lc_channel_t * lc_channel_sidehash(lc_channel_t *base, unsigned char *key, size_t keylen)
{
#ifndef HASH_TYPE
	return (errno = ENOTSUP), NULL;
#else
	lc_ctx_t *ctx = base->ctx;
	lc_channel_t *side = lc_channel_copy(ctx, base);
	if (!side) return NULL;
	hash_generic_key(side->hash, HASHSIZE, base->hash, HASHSIZE, key, keylen);
	/* force regeneration of IPv6 group address */
	memset(&side->sa, 0, sizeof(struct sockaddr_in6));
	lc_channel_in6addr(side);
	return side;
#endif
}

lc_channel_t * lc_channel_sideband(lc_channel_t *base, uint64_t band)
{
	struct in6_addr *in;
	uint64_t *ptr;
	lc_ctx_t *ctx = base->ctx;
	lc_channel_t *side = lc_channel_copy(ctx, base);
	if (!side) return NULL;
	in = &side->sa.sin6_addr;
	ptr = (uint64_t *)&in->s6_addr[8];
	*ptr = band;
	return side;
}

lc_channel_t * lc_channel_copy(lc_ctx_t *ctx, lc_channel_t *chan)
{
	lc_channel_t *copy = malloc(sizeof(lc_channel_t));
	if (!copy) return NULL;
	memset(copy, 0, sizeof(lc_channel_t));
	memcpy(copy->hash, chan->hash, HASHSIZE);
	copy->ctx = ctx;
	copy->sa = chan->sa;
	copy->coding = chan->coding;
	copy->key = chan->key;
	copy->pek = chan->pek;
	copy->sek = chan->sek;
	copy->ssk = chan->ssk;
	clock_gettime(CLOCK_TAI, &chan->byt_reset);
	return lc_channel_ins(ctx, copy);
}

lc_channel_t * lc_channel_nnew(lc_ctx_t *ctx, unsigned char *s, size_t len)
{
	lc_channel_t *chan;
	chan = malloc(sizeof(lc_channel_t));
	if (!chan) return NULL;
	memset(chan, 0, sizeof(lc_channel_t));
	hash_generic(chan->hash, HASHSIZE, s, len);
	chan->ctx = ctx;
	chan->bps_out = ctx->bps_out;
	chan->bps_in = ctx->bps_in;
	chan->coding = ctx->coding;
	chan->key = ctx->key;
	chan->pek = ctx->pek;
	chan->sek = ctx->sek;
	chan->psk = ctx->psk;
	chan->ssk = ctx->ssk;
	clock_gettime(CLOCK_TAI, &chan->byt_reset);
#ifdef HAVE_LIBLCRQ
	chan->T = LC_DEFAULT_RQ_T;
#endif
	return lc_channel_ins(ctx, chan);
}

lc_channel_t * lc_channel_new(lc_ctx_t *ctx, char *s)
{
	return lc_channel_nnew(ctx, (unsigned char *)s, strlen(s));
}

lc_channel_t *lc_channel_init(lc_ctx_t *ctx, struct sockaddr_in6 *sa)
{
	lc_channel_t *chan;
	chan = lc_channel_nnew(ctx, (unsigned char *)&sa->sin6_addr + 2, 14);
	if (!chan) return NULL;
	chan->sa = *sa;
	return chan;
}

lc_channel_t *lc_channel_init_grp(lc_ctx_t *ctx, struct in6_addr *grp, short port)
{
	struct sockaddr_in6 sa = {
		.sin6_family = AF_INET6,
		.sin6_port = htons(port)
	};
	sa.sin6_addr = *grp;
	return lc_channel_init(ctx, &sa);
}

void lc_hashtoaddr(struct in6_addr *addr, unsigned char *hash, size_t hashlen, unsigned char flags)
{
	addr->s6_addr[0] = 0xff;  /* multicast */
	addr->s6_addr[1] = flags; /* set flags */
	memcpy(&addr->s6_addr[2], hash, MIN(hashlen, 14)); /* 112 bits (14 bytes) for hash */
}

lc_channel_t * lc_channel_hash(lc_ctx_t *ctx, unsigned char *hash, size_t hashlen, unsigned char flags,
		short port)
{
	struct sockaddr_in6 sa = {
		.sin6_family = AF_INET6,
		.sin6_port = htons(port)
	};
	lc_hashtoaddr(&sa.sin6_addr, hash, hashlen, flags);
	return lc_channel_init(ctx, &sa);
}

lc_channel_t *lc_channel_random(lc_ctx_t *ctx)
{
	unsigned char buf[14];
	if (lc_getrandom(buf, sizeof buf) != sizeof buf) return NULL;
	return lc_channel_nnew(ctx, buf, sizeof buf);
}

#ifndef IPV6_MULTICAST_ALL
static int lc_socket_groups_free(lc_socket_t *sock)
{
	lc_grplist_t *tmp;
	for (lc_grplist_t *g = sock->grps; g; ) {
		tmp = g;
		g = g->next;
		free(tmp);
	}
	return 0;
}
#endif

/* remove socket from ctx list */
static void socket_unlink(lc_socket_t *sock)
{
	pthread_mutex_lock(&sock->ctx->mtx);
	lc_socket_t *prev = NULL;
	lc_socket_t *next = aload(&sock->next);
	for (lc_socket_t *p = aload(&sock->ctx->sock_list); p; p = aload(&p->next)) {
		if (p == sock) {
			lc_socket_t **ptr = (prev) ? &prev->next : &sock->ctx->sock_list;
			CAE_loop(ptr, &p, next);
			break;
		}
		prev = p;
	}
	pthread_mutex_unlock(&sock->ctx->mtx);
}

void lc_socket_close(lc_socket_t *sock)
{
	int err = errno;
	if (!sock) return;
#ifndef IPV6_MULTICAST_ALL
	lc_socket_groups_free(sock);
#endif
	lc_socket_listen_cancel(sock);
	socket_unlink(sock);
	int deleted = 0;
	if (CAE(&sock->deleted, &deleted, DELETE_LATER))
		lc_socket_release(sock);
	errno = err;
}

inline static void ctx_free(lc_ctx_t *ctx)
{
	int deleted = aload(&ctx->deleted);
	CAE(&ctx->deleted, &deleted, DELETE_LATER);
	lc_ctx_release(ctx);
}

void lc_ctx_free(lc_ctx_t *ctx)
{
	int err = errno;
	if (ctx) {
		void *p;
		if (ctx->q) lc_ctx_qpool_free(ctx);
		while ((p = aload(&ctx->sock_list))) {
			lc_socket_close(p);
		}
		while ((p = aload(&ctx->chan_list))) {
			lc_channel_free(p);
		}
		while ((p = aload(&ctx->router_list))) {
			lc_router_free(p);
		}
		if (ctx->sock >= 0) close(ctx->sock);
		if (ctx->mdex) mdex_free(ctx->mdex);
		pthread_mutex_destroy(&ctx->mtx);
		ctx_free(ctx);
	}
	errno = err;
}

int lc_socket_bind(lc_socket_t *sock, unsigned int ifx)
{
	if (setsockopt(sock->sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifx, sizeof ifx) == -1) {
		return -1;
	}
	sock->ifx = ifx;
	return 0;
}

static lc_socket_t * lc_socket_nnew(lc_ctx_t *ctx, int type)
{
	lc_socket_t *sock;
	int s = 0, i, err = 0;

	sock = calloc(1, sizeof(lc_socket_t));
	if (!sock) return NULL;
	sock->ctx = ctx;
	sock->type = type;
	sock->readers = 1;
	sock->ttl = DEFAULT_MULTICAST_HOPS;
	if (type == LC_SOCK_IN6) {
		s = socket(AF_INET6, SOCK_DGRAM, 0);
		if (s == -1) {
			err = errno;
			goto err_free_sock;
		}
		sock->sock = s;
#ifdef IPV6_MULTICAST_ALL
		/* available in Linux 4.2 onwards */
		i = 0;
		if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_ALL, &i, sizeof i) == -1) {
			goto err_close_s;
		}
#endif
		i = 1;
		if (setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &i, sizeof i) == -1) {
			goto err_close_s;
		}
		i = DEFAULT_MULTICAST_LOOP;
		if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &i, sizeof i) == -1) {
			goto err_close_s;
		}
		i = DEFAULT_MULTICAST_HOPS;
		if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &i, sizeof i) == -1) {
			goto err_close_s;
		}
		if (ctx->ifx && lc_socket_bind(sock, ctx->ifx)) goto err_close_s;
	}
	sock->next = aload(&ctx->sock_list);
	CAE_loop(&ctx->sock_list, &sock->next, sock);
	return sock;
err_close_s:
	err = errno;
	if (s) close(s);
err_free_sock:
	free(sock);
	return (errno = err), NULL;
}

lc_socket_t * lc_socket_new(lc_ctx_t *ctx)
{
	return lc_socket_nnew(ctx, LC_SOCK_IN6);
}

int lc_socketpair(lc_ctx_t *ctx, lc_socket_t *sock[2])
{
	int sv[2];
	int err;
	sock[0] = lc_socket_nnew(ctx, LC_SOCK_PAIR);
	if (!sock[0]) return -1;
	sock[1] = lc_socket_nnew(ctx, LC_SOCK_PAIR);
	if (!sock[1]) goto err_sock_close;
	err = socketpair(AF_UNIX, SOCK_DGRAM, 0, sv);
	if (err) goto err_sock_close;
	for (int i = 0; i < 2; i++) {
		sock[i]->sock = sv[i];
		sock[i^0]->pair = sock[i^1]; /* pair our socks */
	}
	return 0;
err_sock_close:
	err = errno;
	for (int i = 0; i < 2; i++) lc_socket_close(sock[0]);
	errno = err;
	return -1;
}

q_t *lc_ctx_q(lc_ctx_t *ctx)
{
	return ctx->q;
}

int lc_ctx_qpool_free(lc_ctx_t *ctx)
{
	int err = 0, rc = 0;
	if (q_pool_destroy(ctx->tid, ctx->nthreads))
		err = errno, rc = -1;
	free(ctx->tid); ctx->tid = NULL;
	free(ctx->q); ctx->q = NULL;
	if (err) errno = err;
	return rc;
}

int lc_ctx_qpool_resize(lc_ctx_t *ctx, int nthreads)
{
	pthread_t *tid;
	int rc = 0, newtids;
	if (nthreads == ctx->nthreads) return 0;
	if (nthreads < ctx->nthreads) {
		newtids = ctx->nthreads - nthreads;
		rc = q_pool_destroy(&ctx->tid[nthreads], newtids);
		if (rc) return -1;
		ctx->nthreads = nthreads;
	}
	tid = realloc(ctx->tid, sizeof(pthread_t) * nthreads);
	if (!tid) return -1;
	ctx->tid = tid;
	if (nthreads > ctx->nthreads) {
		newtids = nthreads - ctx->nthreads;
		memset(&tid[ctx->nthreads], 0, sizeof(pthread_t) * newtids);
		rc = q_pool_create(&ctx->tid[ctx->nthreads], newtids, &q_job_seek, ctx->q);
		ctx->nthreads = nthreads - rc;
	}
	return (ctx->nthreads == nthreads) ? 0 : -1;
}

int lc_ctx_qpool_init(lc_ctx_t *ctx, int nthreads)
{
	int err;
	ctx->q = malloc(sizeof(q_t));
	if (!ctx->q) return -1;
	if (q_init(ctx->q)) {
		err = errno;
		goto err_free_ctx_q;
	}
	ctx->tid = calloc(nthreads, sizeof(pthread_t));
	if (!ctx->tid) {
		err = errno;
		goto err_q_free;
	}
	ctx->nthreads = nthreads - q_pool_create(ctx->tid, nthreads, &q_job_seek, ctx->q);
	return (ctx->nthreads == nthreads) ? 0 : -1;
err_q_free:
	q_free(ctx->q);
err_free_ctx_q:
	free(ctx->q);
	return (errno = err), -1;
}

void lc_ctx_release(lc_ctx_t *ctx)
{
	/* decrease reference count, fetch updated value */
	int refs = add_fetch(&ctx->readers, -1);
	int deleted = aload(&ctx->deleted);
	if (!refs && deleted == DELETE_LATER) {
		/* no readers left, try to delete */
		if (CAE(&ctx->deleted, &deleted, DELETE_IN_PROGRESS)) {
			free(ctx);
		}
	}
}

void lc_socket_release(lc_socket_t *sock)
{
	/* decrease reference count, fetch updated value */
	int refs = add_fetch(&sock->readers, -1);
	int deleted = aload(&sock->deleted);
	if (!refs && deleted == DELETE_LATER) {
		/* no readers left, try to delete */
		if (CAE(&sock->deleted, &deleted, DELETE_IN_PROGRESS)) {
			if (sock->sock) close(sock->sock);
			if (sock->oil) free(sock->oil);
			free(sock);
		}
	}
}

void lc_channel_release(lc_channel_t *chan)
{
	/* decrease reference count, fetch updated value */
	int refs = add_fetch(&chan->readers, -1);
	int deleted = aload(&chan->deleted);
	if (!refs && deleted == DELETE_LATER) {
		/* no readers left, try to delete */
		if (CAE(&chan->deleted, &deleted, DELETE_IN_PROGRESS)) {
			free(chan);
		}
	}
}

lc_ctx_t *lc_ctx_acquire(lc_ctx_t *ctx)
{
	if (!ctx || aload(&ctx->deleted)) return NULL;
	fetch_add(&ctx->readers, 1); /* increase reference count */
	return ctx;
}

lc_socket_t *lc_socket_acquire(lc_socket_t *sock)
{
	if (!sock || aload(&sock->deleted)) return NULL;
	fetch_add(&sock->readers, 1); /* increase reference count */
	return sock;
}

lc_channel_t *lc_channel_acquire(lc_channel_t *chan)
{
	if (!chan || aload(&chan->deleted)) return NULL;
	fetch_add(&chan->readers, 1); /* increase reference count */
	return chan;
}

lc_ctx_t * lc_ctx_new(void)
{
	lc_ctx_t *ctx;

	if (!(ctx = calloc(1, sizeof(lc_ctx_t)))) return NULL; /* errno set by calloc */
	ctx->next = aload(&ctx_list);
	astor(&ctx_list, ctx);
	ctx->sock = -1;
	ctx->readers = 1;
	if (pthread_mutex_init(&ctx->mtx, NULL)) {
		free(ctx); return NULL;
	}

	return ctx;
}

int lc_ctx_debug(lc_ctx_t *ctx, int debug)
{
	if (debug != -1) ctx->debug = debug;
	return ctx->debug;
}

FILE *lc_ctx_stream(lc_ctx_t *ctx, FILE *stream)
{
	if (stream) ctx->stream = stream;
	return ctx->stream;
}
