<commit_before>/* See LICENSE file for copyright and license details. */
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <tox/tox.h>
#include <tox/toxav.h>
#include <tox/toxencryptsave.h>

#include "arg.h"
#include "queue.h"
#include "readpassphrase.h"
#include "util.h"

const char *reqerr[] = {
	[-TOX_FAERR_TOOLONG]      = "Message is too long",
	[-TOX_FAERR_NOMESSAGE]    = "Please add a message to your request",
	[-TOX_FAERR_OWNKEY]       = "That appears to be your own ID",
	[-TOX_FAERR_ALREADYSENT]  = "Friend request already sent",
	[-TOX_FAERR_UNKNOWN]      = "Unknown error while sending your request",
	[-TOX_FAERR_BADCHECKSUM]  = "Bad checksum while verifying address",
	[-TOX_FAERR_SETNEWNOSPAM] = "Friend already added but nospam doesn't match",
	[-TOX_FAERR_NOMEM]        = "Error increasing the friend list size"
};

struct node {
	char *addr4;
	char *addr6;
	uint16_t port;
	char *idstr;
};

#include "config.h"

struct file {
	int type;
	const char *name;
	int flags;
};

enum {
	NONE,
	FIFO,
	STATIC,
	FOLDER
};

enum {
	IN,
	OUT,
	ERR,
};

static struct file gfiles[] = {
	[IN]      = { .type = FIFO,   .name = "in",  .flags = O_RDONLY | O_NONBLOCK,       },
	[OUT]     = { .type = NONE,   .name = "out", .flags = O_WRONLY | O_TRUNC | O_CREAT },
	[ERR]     = { .type = STATIC, .name = "err", .flags = O_WRONLY | O_TRUNC | O_CREAT },
};

static int idfd = -1;

struct slot {
	const char *name;
	void (*cb)(void *);
	int outisfolder;
	int dirfd;
	int fd[LEN(gfiles)];
};

static void setname(void *);
static void setstatus(void *);
static void setuserstate(void *);
static void sendfriendreq(void *);
static void setnospam(void *);

enum {
	NAME,
	STATUS,
	STATE,
	REQUEST,
	NOSPAM
};

static struct slot gslots[] = {
	[NAME]    = { .name = "name",	 .cb = setname,	      .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} },
	[STATUS]  = { .name = "status",	 .cb = setstatus,     .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} },
	[STATE]   = { .name = "state",   .cb = setuserstate,  .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} },
	[REQUEST] = { .name = "request", .cb = sendfriendreq, .outisfolder = 1, .dirfd = -1, .fd = {-1, -1, -1} },
	[NOSPAM]  = { .name = "nospam",  .cb = setnospam,     .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} }
};

enum {
	FTEXT_IN,
	FFILE_IN,
	FCALL_IN,
	FTEXT_OUT,
	FFILE_OUT,
	FCALL_OUT,
	FREMOVE,
	FONLINE,
	FNAME,
	FSTATUS,
	FSTATE,
	FFILE_PENDING,
	FCALL_PENDING,
};

static struct file ffiles[] = {
	[FTEXT_IN]      = { .type = FIFO,   .name = "text_in",      .flags = O_RDONLY | O_NONBLOCK         },
	[FFILE_IN]      = { .type = FIFO,   .name = "file_in",      .flags = O_RDONLY | O_NONBLOCK         },
	[FCALL_IN]      = { .type = FIFO,   .name = "call_in",      .flags = O_RDONLY | O_NONBLOCK         },
	[FTEXT_OUT]     = { .type = STATIC, .name = "text_out",     .flags = O_WRONLY | O_APPEND | O_CREAT },
	[FFILE_OUT]     = { .type = FIFO,   .name = "file_out",     .flags = O_WRONLY | O_NONBLOCK         },
	[FCALL_OUT]     = { .type = FIFO,   .name = "call_out",     .flags = O_WRONLY | O_NONBLOCK         },
	[FREMOVE]       = { .type = FIFO,   .name = "remove",       .flags = O_RDONLY | O_NONBLOCK         },
	[FONLINE]       = { .type = STATIC, .name = "online",       .flags = O_WRONLY | O_TRUNC  | O_CREAT },
	[FNAME]         = { .type = STATIC, .name = "name",         .flags = O_WRONLY | O_TRUNC  | O_CREAT },
	[FSTATUS]       = { .type = STATIC, .name = "status",       .flags = O_WRONLY | O_TRUNC  | O_CREAT },
	[FSTATE]        = { .type = STATIC, .name = "state",        .flags = O_WRONLY | O_TRUNC  | O_CREAT },
	[FFILE_PENDING] = { .type = STATIC, .name = "file_pending", .flags = O_WRONLY | O_TRUNC  | O_CREAT },
	[FCALL_PENDING] = { .type = STATIC, .name = "call_pending", .flags = O_WRONLY | O_TRUNC  | O_CREAT },
};

static char *ustate[] = {
	[TOX_USERSTATUS_NONE]    = "none",
	[TOX_USERSTATUS_AWAY]    = "away",
	[TOX_USERSTATUS_BUSY]    = "busy",
	[TOX_USERSTATUS_INVALID] = "invalid"
};

enum {
	TRANSFER_NONE,
	TRANSFER_INITIATED,
	TRANSFER_INPROGRESS,
	TRANSFER_PAUSED,
};

struct transfer {
	uint8_t fnum;
	uint8_t *buf;
	int chunksz;
	ssize_t n;
	int pendingbuf;
	int state;
};

struct call {
	int num;
	ToxAvCallState state;
	uint8_t *frame;
	uint8_t payload[RTP_PAYLOAD_SIZE];
	ssize_t n;
	int incompleteframe;
	struct timespec lastsent;
};

struct friend {
	char name[TOX_MAX_NAME_LENGTH + 1];
	int32_t num;
	uint8_t id[TOX_CLIENT_ID_SIZE];
	char idstr[2 * TOX_CLIENT_ID_SIZE + 1];
	int dirfd;
	int fd[LEN(ffiles)];
	struct transfer tx;
	int rxstate;
	struct call av;
	TAILQ_ENTRY(friend) entry;
};

struct request {
	uint8_t id[TOX_CLIENT_ID_SIZE];
	char idstr[2 * TOX_CLIENT_ID_SIZE + 1];
	char *msg;
	int fd;
	TAILQ_ENTRY(request) entry;
};

static TAILQ_HEAD(friendhead, friend) friendhead = TAILQ_HEAD_INITIALIZER(friendhead);
static TAILQ_HEAD(reqhead, request) reqhead = TAILQ_HEAD_INITIALIZER(reqhead);

static Tox *tox;
static ToxAv *toxav;
static ToxAvCSettings toxavconfig;
static int framesize;
static Tox_Options toxopt;
static uint8_t *passphrase;
static uint32_t pplen;
static volatile sig_atomic_t running = 1;
static int ipv6;
static int tcpflag;
static int proxyflag;

static struct timespec timediff(struct timespec, struct timespec);
static void printrat(void);
static void printout(const char *, ...);
static void fiforeset(int, int *, struct file);
static ssize_t fiforead(int, int *, struct file, void *, size_t);
static void cbcallinvite(void *, int32_t, void *);
static void cbcallstarted(void *, int32_t, void *);
static void cbcallcancelled(void *, int32_t, void *);
static void cbcallrejected(void *, int32_t, void *);
static void cbcallended(void *, int32_t, void *);
static void cbcallringing(void *, int32_t, void *);
static void preparetxcall(struct friend *);
static void cbcallstarting(void *, int32_t, void *);
static void cbcallending(void *, int32_t, void *);
static void cbreqtimeout(void *, int32_t, void *);
static void cbpeertimeout(void *, int32_t, void *);
static void cbcalltypechange(void *, int32_t, void *);
static void cbcalldata(ToxAv *, int32_t, int16_t *, int, void *);
static void cancelrxcall(struct friend *, char *);
static void canceltxcall(struct friend *, char *);
static void sendfriendcalldata(struct friend *);
static void cbconnstatus(Tox *, int32_t, uint8_t, void *);
static void cbfriendmessage(Tox *, int32_t, const uint8_t *, uint16_t, void *);
static void cbfriendrequest(Tox *, const uint8_t *, const uint8_t *, uint16_t, void *);
static void cbnamechange(Tox *, int32_t, const uint8_t *, uint16_t, void *);
static void cbstatusmessage(Tox *, int32_t, const uint8_t *, uint16_t, void *);
static void cbuserstate(Tox *, int32_t, uint8_t, void *);
static void cbfilecontrol(Tox *, int32_t, uint8_t, uint8_t, uint8_t, const uint8_t *, uint16_t, void *);
static void cbfilesendreq(Tox *, int32_t, uint8_t, uint64_t, const uint8_t *, uint16_t, void *);
static void cbfiledata(Tox *, int32_t, uint8_t, const uint8_t *, uint16_t, void *);
static void canceltxtransfer(struct friend *);
static void cancelrxtransfer(struct friend *);
static void sendfriendfile(struct friend *);
static void sendfriendtext(struct friend *);
static void removefriend(struct friend *);
static int readpass(const char *);
static void dataload(void);
static void datasave(void);
static int localinit(void);
static int toxinit(void);
static int toxconnect(void);
static void id2str(uint8_t *, char *);
static void str2id(char *, uint8_t *);
static struct friend *friendcreate(int32_t);
static void friendload(void);
static void frienddestroy(struct friend *);
static void loop(void);
static void initshutdown(int);
static void shutdown(void);
static void usage(void);

static struct timespec
timediff(struct timespec t1, struct timespec t2)
{
	struct timespec tmp;

	tmp.tv_sec = t2.tv_sec - t1.tv_sec;

	if ((t2.tv_nsec - t1.tv_nsec) > 0) {
		tmp.tv_nsec = (t2.tv_nsec - t1.tv_nsec);
	} else {
		tmp.tv_nsec = 1E9 - (t1.tv_nsec - t2.tv_nsec);
		tmp.tv_sec--;
	}

	return tmp;
}

static void
printrat(void)
{
	printf(	"\033[31m"
		"                /y\\            /y\\\n"
		"               /ver\\          /"VERSION"\\\n"
		"               yyyyyy\\      /yyyyyy\n"
		"               \\yyyyyyyyyyyyyyyyyy/\n"
		"                yyyyyyyyyyyyyyyyyy\n"
		"                yyyyyyyyyyyyyyyyyy\n"
		"                yyy'yyyyyyyyyy'yyy\n"
		"                \\yy  yyyyyyyy  yy/\n"
		"                 \\yy.yyyyyyyy.yy/\n"
		"                  \\yyyyyyyyyyyy/\n"
		"                    \\yyyyyyyy/\n"
		"              -------yyyyyyyy-------\n"
		"                 ..---yyyyyy---..\n"
		"                   ..--yyyy--..\n"
		"\033[0m\n");
}

static void
printout(const char *fmt, ...)
{
	va_list ap;
	char buft[64];
	time_t t;

	va_start(ap, fmt);
	t = time(NULL);
	strftime(buft, sizeof(buft), "%F %R", localtime(&t));
	printf("%s ", buft);
	vfprintf(stdout, fmt, ap);
	va_end(ap);
}

static void
fiforeset(int dirfd, int *fd, struct file f)
{
	ssize_t r;

	unlinkat(dirfd, f.name, 0);
	if (*fd != -1)
		close(*fd);
	r = mkfifoat(dirfd, f.name, 0666);
	if (r < 0 && errno != EEXIST)
		eprintf("mkfifoat %s:", f.name);
	r = openat(dirfd, f.name, f.flags);
	if (r < 0 && errno != ENXIO)
		eprintf("openat %s:", f.name);
	*fd = r;
}

static ssize_t
fiforead(int dirfd, int *fd, struct file f, void *buf, size_t sz)
{
	ssize_t r;

again:
	r = read(*fd, buf, sz);
	if (r == 0) {
		fiforeset(dirfd, fd, f);
		return 0;
	}
	if (r < 0) {
		if (errno == EINTR)
			goto again;
		if (errno == EWOULDBLOCK)
			return -1;
		eprintf("read %s:", f.name);
	}
	return r;
}

static void
cbcallinvite(void *av, int32_t cnum, void *udata)
{
	ToxAvCSettings avconfig;
	struct friend *f;
	int32_t fnum;
	int r;

	fnum = toxav_get_peer_id(toxav, cnum, 0);
	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->num == fnum)
			break;
	if (!f)
		return;

	f->av.num = cnum;
	r = toxav_get_peer_csettings(toxav, cnum, 0, &avconfig);
	if (r < 0) {
		weprintf("Failed to determine peer call type\n");
		return;
	}

	switch (avconfig.call_type) {
	case TypeVideo:
		printout(": %s : Rx AV > Inviting with video\n", f->name);
		break;
	case TypeAudio:
		printout(": %s : Rx AV > Inviting without video\n", f->name);
		break;
	}

	printout(": %s : Rx AV > Audio call settings: srate = %lu, channels = %lu\n",
		 f->name, avconfig.audio_sample_rate, avconfig.audio_channels);

	ftruncate(f->fd[FCALL_PENDING], 0);
	lseek(f->fd[FCALL_PENDING], 0, SEEK_SET);
	dprintf(f->fd[FCALL_PENDING], "1\n");

	f->av.state = av_CallStarting;
}

static void
cbcallstarted(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	printout(": %s : Rx AV > Started\n", f->name);

	toxav_prepare_transmission(toxav, cnum, av_jbufdc, av_VADd, 0);

	f->av.state = av_CallActive;
}

static void
cbcallended(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	cancelrxcall(f, "Ended");
	canceltxcall(f, "Ended");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbcallcancelled(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	cancelrxcall(f, "Cancelled");
	canceltxcall(f, "Cancelled");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbcallrejected(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	canceltxcall(f, "Rejected");
	cancelrxcall(f, "Rejected");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbcallringing(void *av, int32_t cnum, void *udata)
{
	return;
}

static void
preparetxcall(struct friend *f)
{
	f->av.frame = malloc(sizeof(int16_t) * framesize);
	if (!f->av.frame)
		eprintf("malloc:");
	f->av.n = 0;
	f->av.incompleteframe = 0;
	f->av.lastsent.tv_sec = 0;
	f->av.lastsent.tv_nsec = 0;
	f->av.state = av_CallActive;
}

static void
cbcallstarting(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	printout(": %s : Tx AV > Started\n", f->name);
	preparetxcall(f);
	toxav_prepare_transmission(toxav, cnum, av_jbufdc, av_VADd, 0);
}

static void
cbcallending(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	cancelrxcall(f, "Ending");
	canceltxcall(f, "Ending");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbreqtimeout(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;
	cancelrxcall(f, "Timeout");
	canceltxcall(f, "Timeout");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbpeertimeout(void *av, int32_t cnum, void *udata)
{
	printf("Entered %s\n", __func__);
}

static void
cbcalltypechange(void *av, int32_t cnum, void *udata)
{
	printf("Entered %s\n", __func__);<commit_msg>Implement peer timeout<commit_after>/* See LICENSE file for copyright and license details. */
#include <sys/select.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

#include <tox/tox.h>
#include <tox/toxav.h>
#include <tox/toxencryptsave.h>

#include "arg.h"
#include "queue.h"
#include "readpassphrase.h"
#include "util.h"

const char *reqerr[] = {
	[-TOX_FAERR_TOOLONG]      = "Message is too long",
	[-TOX_FAERR_NOMESSAGE]    = "Please add a message to your request",
	[-TOX_FAERR_OWNKEY]       = "That appears to be your own ID",
	[-TOX_FAERR_ALREADYSENT]  = "Friend request already sent",
	[-TOX_FAERR_UNKNOWN]      = "Unknown error while sending your request",
	[-TOX_FAERR_BADCHECKSUM]  = "Bad checksum while verifying address",
	[-TOX_FAERR_SETNEWNOSPAM] = "Friend already added but nospam doesn't match",
	[-TOX_FAERR_NOMEM]        = "Error increasing the friend list size"
};

struct node {
	char *addr4;
	char *addr6;
	uint16_t port;
	char *idstr;
};

#include "config.h"

struct file {
	int type;
	const char *name;
	int flags;
};

enum {
	NONE,
	FIFO,
	STATIC,
	FOLDER
};

enum {
	IN,
	OUT,
	ERR,
};

static struct file gfiles[] = {
	[IN]      = { .type = FIFO,   .name = "in",  .flags = O_RDONLY | O_NONBLOCK,       },
	[OUT]     = { .type = NONE,   .name = "out", .flags = O_WRONLY | O_TRUNC | O_CREAT },
	[ERR]     = { .type = STATIC, .name = "err", .flags = O_WRONLY | O_TRUNC | O_CREAT },
};

static int idfd = -1;

struct slot {
	const char *name;
	void (*cb)(void *);
	int outisfolder;
	int dirfd;
	int fd[LEN(gfiles)];
};

static void setname(void *);
static void setstatus(void *);
static void setuserstate(void *);
static void sendfriendreq(void *);
static void setnospam(void *);

enum {
	NAME,
	STATUS,
	STATE,
	REQUEST,
	NOSPAM
};

static struct slot gslots[] = {
	[NAME]    = { .name = "name",	 .cb = setname,	      .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} },
	[STATUS]  = { .name = "status",	 .cb = setstatus,     .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} },
	[STATE]   = { .name = "state",   .cb = setuserstate,  .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} },
	[REQUEST] = { .name = "request", .cb = sendfriendreq, .outisfolder = 1, .dirfd = -1, .fd = {-1, -1, -1} },
	[NOSPAM]  = { .name = "nospam",  .cb = setnospam,     .outisfolder = 0, .dirfd = -1, .fd = {-1, -1, -1} }
};

enum {
	FTEXT_IN,
	FFILE_IN,
	FCALL_IN,
	FTEXT_OUT,
	FFILE_OUT,
	FCALL_OUT,
	FREMOVE,
	FONLINE,
	FNAME,
	FSTATUS,
	FSTATE,
	FFILE_PENDING,
	FCALL_PENDING,
};

static struct file ffiles[] = {
	[FTEXT_IN]      = { .type = FIFO,   .name = "text_in",      .flags = O_RDONLY | O_NONBLOCK         },
	[FFILE_IN]      = { .type = FIFO,   .name = "file_in",      .flags = O_RDONLY | O_NONBLOCK         },
	[FCALL_IN]      = { .type = FIFO,   .name = "call_in",      .flags = O_RDONLY | O_NONBLOCK         },
	[FTEXT_OUT]     = { .type = STATIC, .name = "text_out",     .flags = O_WRONLY | O_APPEND | O_CREAT },
	[FFILE_OUT]     = { .type = FIFO,   .name = "file_out",     .flags = O_WRONLY | O_NONBLOCK         },
	[FCALL_OUT]     = { .type = FIFO,   .name = "call_out",     .flags = O_WRONLY | O_NONBLOCK         },
	[FREMOVE]       = { .type = FIFO,   .name = "remove",       .flags = O_RDONLY | O_NONBLOCK         },
	[FONLINE]       = { .type = STATIC, .name = "online",       .flags = O_WRONLY | O_TRUNC  | O_CREAT },
	[FNAME]         = { .type = STATIC, .name = "name",         .flags = O_WRONLY | O_TRUNC  | O_CREAT },
	[FSTATUS]       = { .type = STATIC, .name = "status",       .flags = O_WRONLY | O_TRUNC  | O_CREAT },
	[FSTATE]        = { .type = STATIC, .name = "state",        .flags = O_WRONLY | O_TRUNC  | O_CREAT },
	[FFILE_PENDING] = { .type = STATIC, .name = "file_pending", .flags = O_WRONLY | O_TRUNC  | O_CREAT },
	[FCALL_PENDING] = { .type = STATIC, .name = "call_pending", .flags = O_WRONLY | O_TRUNC  | O_CREAT },
};

static char *ustate[] = {
	[TOX_USERSTATUS_NONE]    = "none",
	[TOX_USERSTATUS_AWAY]    = "away",
	[TOX_USERSTATUS_BUSY]    = "busy",
	[TOX_USERSTATUS_INVALID] = "invalid"
};

enum {
	TRANSFER_NONE,
	TRANSFER_INITIATED,
	TRANSFER_INPROGRESS,
	TRANSFER_PAUSED,
};

struct transfer {
	uint8_t fnum;
	uint8_t *buf;
	int chunksz;
	ssize_t n;
	int pendingbuf;
	int state;
};

struct call {
	int num;
	ToxAvCallState state;
	uint8_t *frame;
	uint8_t payload[RTP_PAYLOAD_SIZE];
	ssize_t n;
	int incompleteframe;
	struct timespec lastsent;
};

struct friend {
	char name[TOX_MAX_NAME_LENGTH + 1];
	int32_t num;
	uint8_t id[TOX_CLIENT_ID_SIZE];
	char idstr[2 * TOX_CLIENT_ID_SIZE + 1];
	int dirfd;
	int fd[LEN(ffiles)];
	struct transfer tx;
	int rxstate;
	struct call av;
	TAILQ_ENTRY(friend) entry;
};

struct request {
	uint8_t id[TOX_CLIENT_ID_SIZE];
	char idstr[2 * TOX_CLIENT_ID_SIZE + 1];
	char *msg;
	int fd;
	TAILQ_ENTRY(request) entry;
};

static TAILQ_HEAD(friendhead, friend) friendhead = TAILQ_HEAD_INITIALIZER(friendhead);
static TAILQ_HEAD(reqhead, request) reqhead = TAILQ_HEAD_INITIALIZER(reqhead);

static Tox *tox;
static ToxAv *toxav;
static ToxAvCSettings toxavconfig;
static int framesize;
static Tox_Options toxopt;
static uint8_t *passphrase;
static uint32_t pplen;
static volatile sig_atomic_t running = 1;
static int ipv6;
static int tcpflag;
static int proxyflag;

static struct timespec timediff(struct timespec, struct timespec);
static void printrat(void);
static void printout(const char *, ...);
static void fiforeset(int, int *, struct file);
static ssize_t fiforead(int, int *, struct file, void *, size_t);
static void cbcallinvite(void *, int32_t, void *);
static void cbcallstarted(void *, int32_t, void *);
static void cbcallcancelled(void *, int32_t, void *);
static void cbcallrejected(void *, int32_t, void *);
static void cbcallended(void *, int32_t, void *);
static void cbcallringing(void *, int32_t, void *);
static void preparetxcall(struct friend *);
static void cbcallstarting(void *, int32_t, void *);
static void cbcallending(void *, int32_t, void *);
static void cbreqtimeout(void *, int32_t, void *);
static void cbpeertimeout(void *, int32_t, void *);
static void cbcalltypechange(void *, int32_t, void *);
static void cbcalldata(ToxAv *, int32_t, int16_t *, int, void *);
static void cancelrxcall(struct friend *, char *);
static void canceltxcall(struct friend *, char *);
static void sendfriendcalldata(struct friend *);
static void cbconnstatus(Tox *, int32_t, uint8_t, void *);
static void cbfriendmessage(Tox *, int32_t, const uint8_t *, uint16_t, void *);
static void cbfriendrequest(Tox *, const uint8_t *, const uint8_t *, uint16_t, void *);
static void cbnamechange(Tox *, int32_t, const uint8_t *, uint16_t, void *);
static void cbstatusmessage(Tox *, int32_t, const uint8_t *, uint16_t, void *);
static void cbuserstate(Tox *, int32_t, uint8_t, void *);
static void cbfilecontrol(Tox *, int32_t, uint8_t, uint8_t, uint8_t, const uint8_t *, uint16_t, void *);
static void cbfilesendreq(Tox *, int32_t, uint8_t, uint64_t, const uint8_t *, uint16_t, void *);
static void cbfiledata(Tox *, int32_t, uint8_t, const uint8_t *, uint16_t, void *);
static void canceltxtransfer(struct friend *);
static void cancelrxtransfer(struct friend *);
static void sendfriendfile(struct friend *);
static void sendfriendtext(struct friend *);
static void removefriend(struct friend *);
static int readpass(const char *);
static void dataload(void);
static void datasave(void);
static int localinit(void);
static int toxinit(void);
static int toxconnect(void);
static void id2str(uint8_t *, char *);
static void str2id(char *, uint8_t *);
static struct friend *friendcreate(int32_t);
static void friendload(void);
static void frienddestroy(struct friend *);
static void loop(void);
static void initshutdown(int);
static void shutdown(void);
static void usage(void);

static struct timespec
timediff(struct timespec t1, struct timespec t2)
{
	struct timespec tmp;

	tmp.tv_sec = t2.tv_sec - t1.tv_sec;

	if ((t2.tv_nsec - t1.tv_nsec) > 0) {
		tmp.tv_nsec = (t2.tv_nsec - t1.tv_nsec);
	} else {
		tmp.tv_nsec = 1E9 - (t1.tv_nsec - t2.tv_nsec);
		tmp.tv_sec--;
	}

	return tmp;
}

static void
printrat(void)
{
	printf(	"\033[31m"
		"                /y\\            /y\\\n"
		"               /ver\\          /"VERSION"\\\n"
		"               yyyyyy\\      /yyyyyy\n"
		"               \\yyyyyyyyyyyyyyyyyy/\n"
		"                yyyyyyyyyyyyyyyyyy\n"
		"                yyyyyyyyyyyyyyyyyy\n"
		"                yyy'yyyyyyyyyy'yyy\n"
		"                \\yy  yyyyyyyy  yy/\n"
		"                 \\yy.yyyyyyyy.yy/\n"
		"                  \\yyyyyyyyyyyy/\n"
		"                    \\yyyyyyyy/\n"
		"              -------yyyyyyyy-------\n"
		"                 ..---yyyyyy---..\n"
		"                   ..--yyyy--..\n"
		"\033[0m\n");
}

static void
printout(const char *fmt, ...)
{
	va_list ap;
	char buft[64];
	time_t t;

	va_start(ap, fmt);
	t = time(NULL);
	strftime(buft, sizeof(buft), "%F %R", localtime(&t));
	printf("%s ", buft);
	vfprintf(stdout, fmt, ap);
	va_end(ap);
}

static void
fiforeset(int dirfd, int *fd, struct file f)
{
	ssize_t r;

	unlinkat(dirfd, f.name, 0);
	if (*fd != -1)
		close(*fd);
	r = mkfifoat(dirfd, f.name, 0666);
	if (r < 0 && errno != EEXIST)
		eprintf("mkfifoat %s:", f.name);
	r = openat(dirfd, f.name, f.flags);
	if (r < 0 && errno != ENXIO)
		eprintf("openat %s:", f.name);
	*fd = r;
}

static ssize_t
fiforead(int dirfd, int *fd, struct file f, void *buf, size_t sz)
{
	ssize_t r;

again:
	r = read(*fd, buf, sz);
	if (r == 0) {
		fiforeset(dirfd, fd, f);
		return 0;
	}
	if (r < 0) {
		if (errno == EINTR)
			goto again;
		if (errno == EWOULDBLOCK)
			return -1;
		eprintf("read %s:", f.name);
	}
	return r;
}

static void
cbcallinvite(void *av, int32_t cnum, void *udata)
{
	ToxAvCSettings avconfig;
	struct friend *f;
	int32_t fnum;
	int r;

	fnum = toxav_get_peer_id(toxav, cnum, 0);
	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->num == fnum)
			break;
	if (!f)
		return;

	f->av.num = cnum;
	r = toxav_get_peer_csettings(toxav, cnum, 0, &avconfig);
	if (r < 0) {
		weprintf("Failed to determine peer call type\n");
		return;
	}

	switch (avconfig.call_type) {
	case TypeVideo:
		printout(": %s : Rx AV > Inviting with video\n", f->name);
		break;
	case TypeAudio:
		printout(": %s : Rx AV > Inviting without video\n", f->name);
		break;
	}

	printout(": %s : Rx AV > Audio call settings: srate = %lu, channels = %lu\n",
		 f->name, avconfig.audio_sample_rate, avconfig.audio_channels);

	ftruncate(f->fd[FCALL_PENDING], 0);
	lseek(f->fd[FCALL_PENDING], 0, SEEK_SET);
	dprintf(f->fd[FCALL_PENDING], "1\n");

	f->av.state = av_CallStarting;
}

static void
cbcallstarted(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	printout(": %s : Rx AV > Started\n", f->name);

	toxav_prepare_transmission(toxav, cnum, av_jbufdc, av_VADd, 0);

	f->av.state = av_CallActive;
}

static void
cbcallended(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	cancelrxcall(f, "Ended");
	canceltxcall(f, "Ended");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbcallcancelled(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	cancelrxcall(f, "Cancelled");
	canceltxcall(f, "Cancelled");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbcallrejected(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	canceltxcall(f, "Rejected");
	cancelrxcall(f, "Rejected");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbcallringing(void *av, int32_t cnum, void *udata)
{
	return;
}

static void
preparetxcall(struct friend *f)
{
	f->av.frame = malloc(sizeof(int16_t) * framesize);
	if (!f->av.frame)
		eprintf("malloc:");
	f->av.n = 0;
	f->av.incompleteframe = 0;
	f->av.lastsent.tv_sec = 0;
	f->av.lastsent.tv_nsec = 0;
	f->av.state = av_CallActive;
}

static void
cbcallstarting(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	printout(": %s : Tx AV > Started\n", f->name);
	preparetxcall(f);
	toxav_prepare_transmission(toxav, cnum, av_jbufdc, av_VADd, 0);
}

static void
cbcallending(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;

	cancelrxcall(f, "Ending");
	canceltxcall(f, "Ending");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbreqtimeout(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;
	cancelrxcall(f, "Request timeout");
	canceltxcall(f, "Request timeout");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbpeertimeout(void *av, int32_t cnum, void *udata)
{
	struct friend *f;

	TAILQ_FOREACH(f, &friendhead, entry)
		if (f->av.num == cnum)
			break;
	if (!f)
		return;
	cancelrxcall(f, "Peer timeout");
	canceltxcall(f, "Peer timeout");
	toxav_kill_transmission(toxav, cnum);
}

static void
cbcalltypechange(void *av, int32_t cnum, void *udata)
{
	printf("Entered %s\n", __func__);
{'fraction_non_alphanumeric': 0.1542645698427382, 'fraction_numerical': 0.014875115633672525, 'mean_word_length': 3.5315224681421866, 'pattern_counts': {'":': 10, '<': 49, '<?xml version=': 0, '>': 106, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 18, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1702802', 'n_tokens_mistral': 11615, 'n_tokens_neox': 10941, 'n_words': 2649}