/*
 * Asterisk -- A telephony toolkit for Linux.
 *
 * Bluetooth Presense Proxy Channel
 * 
 * Copyright (C) 1999, Mark Spencer
 *
 * Mark Spencer <markster@linux-support.net>
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License
 */

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/signal.h>
#include "asterisk/lock.h"
#include "asterisk/channel.h"
#include "asterisk/config.h"
#include "asterisk/logger.h"
#include "asterisk/module.h"
#include "asterisk/pbx.h"
#include "asterisk/options.h"
#include "asterisk/lock.h"
#include "asterisk/sched.h"
#include "asterisk/io.h"
#include "asterisk/rtp.h"
#include "asterisk/acl.h"
#include "asterisk/callerid.h"
#include "asterisk/file.h"
#include "asterisk/cli.h"
#include "asterisk/app.h"
#include "asterisk/musiconhold.h"
#include "asterisk/manager.h"
#include "asterisk/aes.h"
#include "asterisk/md5.h"
#include "asterisk/utils.h"
#include "btpp.h"

static char *desc = "Bluetooth Presense Proxy Channel";
static const char type[] = "BTP";
static const char tdesc[] = "Bluetooth Presense Channel Driver";

static int usecnt = 0;
static int netfd = -1;
static int btpdebug = 0;
static short oseqno = 0;
static pthread_t netthreadid;
AST_MUTEX_DEFINE_STATIC(usecnt_lock);

#define MAX_LOCATIONS 32			/* Can't be in more than 32 places at once */
#define MAX_LIFE	  60			/* Maximum life of data */
#define BETA			0.33		/* How quickly to adapt to new values */

/* Protect the interface list (of sip_pvt's) */
AST_MUTEX_DEFINE_STATIC(btplock);

struct location {
	struct btp_locator *loc;
	time_t timeout;
	int score;
};

static struct btp_client {
	char alias[80];
	char addr[18];
	char defaultchan[256];
	struct location whereat[MAX_LOCATIONS];
	int dead;
	struct btp_client *next;
} *clients = NULL;

static struct btp_locator {
	char name[80];
	char channel[256];
	aes_decrypt_ctx cx;
	int dead;
	struct btp_locator *next;
} *locators = NULL;

static void md5sum(char *sum, const char *data, int datalen, char *extra)
{
	struct MD5Context md5;
	MD5Init(&md5);
	MD5Update(&md5, data, datalen);
	if (extra)
		MD5Update(&md5, extra, strlen(extra));
	MD5Final(sum, &md5);
}

static struct btp_client *btp_get_client(char *name)
{
	struct btp_client *p;
	p = clients;
	while(p) {
		if (!strcasecmp(p->alias, name)) 
			break;
		p = p->next;
	}
	return p;
}

static struct btp_client *btp_get_client_by_addr(char *addr)
{
	struct btp_client *p;
	p = clients;
	while(p) {
		if (!strcasecmp(p->addr, addr)) 
			break;
		p = p->next;
	}
	return p;
}

static void btp_client_update(char *client, struct btp_locator *locator, int score)
{
	struct btp_client *p;
	struct btp_locator *match = locator;
	int x,y;
	time_t now;
	p = btp_get_client_by_addr(client);
	if (p) {
		time(&now);
		for (y=0;y<2;y++) {
			/* First pass, look for match, second pass, look for empty space */
			for (x=0;x<MAX_LOCATIONS;x++) {
				if (p->whereat[x].loc == match) {
					if (p->whereat[x].timeout >= now) {
						/* Merge score if not yet expired */
						p->whereat[x].score = (((1.0 - BETA) * (float)(p->whereat[x].score)) + ((BETA) * (float)(score)));
					} else {
						/* Set the score */
						p->whereat[x].score = score;
					}
					p->whereat[x].loc = locator;
					p->whereat[x].timeout = now + MAX_LIFE;
					return;
				}
			}
			match = NULL;
		}
	}
	
}

static int send_message(struct sockaddr_in *sin,struct btp_msg *omsg, int bodylen)
{
	int res;
	res = sendto(netfd, omsg, sizeof(struct btp_msg) + bodylen, 0, (struct sockaddr *)sin, sizeof(*sin));
	return res;
}

static int check_report(struct btp_locator *l, unsigned char *buf, int len)
{
	union btp_report_data rep;
	unsigned char sum[16];
	int x;
	if (len != sizeof(union btp_report_data)) {
		/* Wrong length */
		return -1;
	}
	aes_decrypt(buf, rep.raw, &l->cx);
	aes_decrypt(buf + 16, rep.raw + 16, &l->cx);
	md5sum(sum, rep.raw, 24, l->name);
	for (x=0;x<8;x++) {
		/* Verify checksum */
		if (rep.data.pad[x] != (sum[x] ^ sum[x + 8]))
			return -1;
	}
	rep.data.unit[sizeof(rep.data.unit) - 1] = '\0';
	ast_mutex_lock(&btplock);
	btp_client_update(rep.data.unit, l, ntohs(rep.data.score));
	ast_mutex_unlock(&btplock);
#if 0
	printf("Flags: %08x\n", ntohl(rep.data.flags));
	printf("Unit: %s\n",rep.data.unit);
	printf("Score: %d\n", ntohs(rep.data.score));
#endif	
	return 0;
}

static void handle_message(unsigned char *buf, int len, struct sockaddr_in *sin)
{
	char user[80];
	int userpos = 0;
	struct btp_locator *l;
	struct btp_msg *msg;
	short seqno, cmd;
	struct btp_msg omsg;
	char iabuf[INET_ADDRSTRLEN];

	memset(user, 0, sizeof(user));
	memset(&omsg, 0, sizeof(omsg));
	msg = (struct btp_msg *)buf;
	if (len < sizeof(struct btp_msg))
		return;
	buf = msg->data;
	seqno = ntohs(msg->seqno);
	cmd = ntohs(msg->cmd);
	len -= sizeof(struct btp_msg);
	while(*buf && len && (userpos < sizeof(user) - 1)) {
		user[userpos++] = *(buf++);
		len--;
	}
	/* Abort on excessively long username */
	if (*buf || !len) 
		return;
	buf++;
	len--;
	l = locators;
	while(l) {
		if (!strcasecmp(user, l->name))
			break;
		l = l->next;
	}
	if (l) {
		if (btpdebug)
			ast_verbose("Got %d long message from '%s:%d' for %s\n", len, ast_inet_ntoa(iabuf, sizeof(iabuf), sin->sin_addr), ntohs(sin->sin_port), l->name);
		switch(cmd) {
		case BTP_CMD_REPORT:
			if (check_report(l, buf, len))
				omsg.cmd = htons(BTP_CMD_ACK | BTP_CMD_RESPONSE);
			else
				omsg.cmd = htons(BTP_CMD_NAK | BTP_CMD_RESPONSE);
			omsg.seqno = htons(seqno);
			send_message(sin, &omsg, 0);
			break;
		default:
			omsg.cmd = htons(BTP_CMD_UNKNOWN | BTP_CMD_RESPONSE);
			omsg.seqno = htons(seqno);
			send_message(sin, &omsg, 0);
		}
	} else
		printf("Couldn't find '%s'\n", user);
}

static void *network_thread(void *ign)
{
	fd_set rfds;
	int res;
	char buf[1024];
	struct sockaddr_in sin;
	int sinlen;
	for(;;) {
		FD_ZERO(&rfds);
		FD_SET(netfd, &rfds);
		res = select(netfd + 1, &rfds, NULL, NULL, NULL);
		if (res < 0) {
			if (errno != ERESTART)
				ast_log(LOG_WARNING, "Select returned error: %s\n", strerror(errno));
			continue;
		}
		if (FD_ISSET(netfd, &rfds)) {
			sinlen = sizeof(sin);
			res = recvfrom(netfd, buf, sizeof(buf), 0, (struct sockaddr *)&sin, &sinlen);
			if (res > 0) {
				handle_message(buf, res, &sin);
			} else
				ast_log(LOG_WARNING, "Recvfrom received error: %s\n", strerror(errno));
		}
	}
}

static char *btp_find(struct btp_client *p, int *outscore)
{
	char *where;
	int score = 0;
	int x;
	time_t now;
	where = p->defaultchan;
	time(&now);
	for (x=0;x<MAX_LOCATIONS;x++) {
		/* Expire */
		if (p->whereat[x].timeout && (p->whereat[x].timeout < now)) {
			p->whereat[x].timeout = 0;
			p->whereat[x].loc = NULL;
			p->whereat[x].score = 0;
		}
		if (p->whereat[x].score > score) {
			score = p->whereat[x].score;
			where = p->whereat[x].loc->channel;
		}
	}
	if (outscore)
		*outscore = score;
	return where;
	
}

static struct ast_channel *btp_request(const char *type, int format, void *data, int *status)
{
	struct btp_client *p;
	struct ast_channel *chan = NULL;
	char tmp[80] = "";
	char *d;
	ast_mutex_lock(&btplock);
	if ((p = btp_get_client(data))) {
		strncpy(tmp, btp_find(p, NULL), sizeof(tmp) - 1);
	}
	ast_mutex_unlock(&btplock);
	if (strlen(tmp)) {
		d = strchr(tmp, '/');
		if (d) {
			*d = '\0';
			d++;
			ast_log(LOG_DEBUG, "Using '%s' tech, '%s' as proxy for '%s'\n", tmp, d, (char *)data);
			chan = ast_request(tmp, format, d, status);
		} else
			ast_log(LOG_WARNING, "'%s' is not a valid channel!\n", tmp);
	}
	return chan;
}

static int btp_show(int fd, int argc, char **argv)
{
	struct btp_client *p;
	struct btp_locator *l;
	char *s;
	int score;
	if (argc != 2)
		return RESULT_SHOWUSAGE;
	ast_mutex_lock(&btplock);
	p = clients;
	if (clients)
		ast_cli(fd, "Bluetooth Presense Clients:\n");
	else
		ast_cli(fd, "No Bluetooth Presense Clients\n");
	while(p) {
		s = btp_find(p, &score);
		ast_cli(fd, "   %14s/%s -- %s (score=%d)\n", p->alias, p->addr, strlen(s) ? s : "<not found>", score);
		p = p->next;
	}
	if (locators)
		ast_cli(fd, "Bluetooth Presense Locators\n");
	else
		ast_cli(fd, "No Bluetooth Locators\n");
	l = locators;
	while(l) {
		ast_cli(fd, "   %32s -- %s\n", l->name, l->channel);
		l = l->next;
	}
	ast_mutex_unlock(&btplock);
	return RESULT_SUCCESS;
}

static char show_btp_usage[] = 
"Usage: show btp\n"
"       Provides summary information on Bluetooth Presense.\n";

static struct ast_cli_entry cli_show_btp = {
	{ "show", "btp", NULL }, btp_show, 
	"Show status of bluetooth presense clients", show_btp_usage, NULL };

static int start_network_thread(void)
{
	return ast_pthread_create(&netthreadid, NULL, network_thread, NULL);
}

static void build_locator(char *s, int lineno)
{
	char *user, *pass, *chan;
	char tmp[256] = "";
	unsigned char sum[16];
	struct btp_locator *l;
	int new=0;
	strncpy(tmp, s, sizeof(tmp) - 1);
	chan = strrchr(tmp, ',');
	if (chan) {
		*chan = '\0';
		chan++;
	}
	pass = strchr(tmp, ':');
	if (pass) {
		*pass = '\0';
		pass++;
	}
	user = tmp;
	if (!user || !strlen(user) || !pass || !strlen(pass) || !chan || !strlen(chan)) {
		ast_log(LOG_WARNING, "Locator format should be 'user:pass,channel' at line %d\n", lineno);
		return;
	}
	l = locators;
	while(l) {
		if (!strcasecmp(l->name, user)) 
			break;
		l = l->next;
	}
	if (!l) {
		new = 1;
		l = malloc(sizeof(struct btp_locator));
		if (l) 
			memset(l, 0, sizeof(struct btp_locator));
	}
	if (l) {
		strncpy(l->name, user, sizeof(l->name) - 1);
		strncpy(l->channel, chan, sizeof(l->channel) - 1);
		md5sum(sum, pass, strlen(pass), l->name);
		memset(pass, '*', strlen(pass));
		aes_decrypt_key128(sum, &l->cx);
		l->dead = 0;
		if (new) {
			l->next = locators;
			locators = l;
		}
	} else
		ast_log(LOG_WARNING, "Out of memory!\n");
}

static void build_client(char *s, int lineno)
{
	char *user, *alias, *chan;
	char tmp[256] = "";
	struct btp_client *p;
	int new=0;
	strncpy(tmp, s, sizeof(tmp) - 1);
	alias = strchr(tmp, ',');
	if (alias) {
		*alias = '\0';
		alias++;
		chan = strchr(alias, ',');
		if (chan) {
			*chan = '\0';
			chan++;
		}
	}
	user = tmp;
	if (!user || !strlen(user) || !alias || !strlen(alias)) {
		ast_log(LOG_WARNING, "client format should be 'alias,id[,defchan]' at line %d\n", lineno);
		return;
	}
	p = clients;
	while(p) {
		if (!strcasecmp(p->alias, user)) 
			break;
		p = p->next;
	}
	if (!p) {
		new = 1;
		p = malloc(sizeof(struct btp_client));
		if (p) 
			memset(p, 0, sizeof(struct btp_client));
	}
	if (p) {
		strncpy(p->alias, user, sizeof(p->alias) - 1);
		strncpy(p->addr, alias, sizeof(p->addr) - 1);
		if (chan)
			strncpy(p->defaultchan, chan, sizeof(p->defaultchan) - 1);
		else
			strcpy(p->defaultchan, "");
		p->dead = 0;
		if (new) {
			p->next = clients;
			clients = p;
		}
	} else
		ast_log(LOG_WARNING, "Out of memory!\n");
}

static void reap_stuff(void)
{
	ast_log(LOG_NOTICE, "XXX I should reap! XXX\n");
}

static void load_config(void)
{
	struct btp_locator *l;
	struct btp_client *p;
	struct ast_config *cfg;
	struct ast_variable *var;
	cfg = ast_config_load("btp.conf");
	if (!cfg) {
		ast_log(LOG_NOTICE, "No btp.conf file, Bluetooth Presense support disabled!\n");
		return;
	}
	ast_mutex_lock(&btplock);
	/* Mark all locators dead */
	l = locators;
	while(l) {
		l->dead = 1;
		l = l->next;
	}
	/* Mark all clients dead */
	p = clients;
	while(p) {
		p->dead = 1;
		p = p->next;
	}
	var = ast_variable_browse(cfg, "locators");
	while(var) {
		if (!strcasecmp(var->name, "locator"))
			build_locator(var->value, var->lineno);
		var = var->next;
	}
	var = ast_variable_browse(cfg, "clients");
	while(var) {
		if (!strcasecmp(var->name, "client"))
			build_client(var->value, var->lineno);
		var = var->next;
	}
	reap_stuff();
	ast_mutex_unlock(&btplock);
	ast_config_destroy(cfg);
}

static struct ast_channel_tech btptech = {
	.type = type,
	.description = tdesc,
	.capabilities = -1,
	.requester = btp_request,
};

int load_module()
{
	struct sockaddr_in sin;
	/* Make sure we can register our BTP channel type */
	if (ast_channel_register(&btptech)) {
		ast_log(LOG_ERROR, "Unable to register channel class %s\n", type);
		return -1;
	}
	if (netfd < 0) {
		netfd = socket(AF_INET, SOCK_DGRAM, 0);
		if (netfd > -1) {
			memset(&sin, 0, sizeof(sin));
			sin.sin_family = AF_INET;
			sin.sin_port = htons(BTP_PORT);
			if (bind(netfd, (struct sockaddr *)&sin, sizeof(sin))) {
				ast_log(LOG_WARNING, "Unable to bind port: %s\n", strerror(errno));
				close(netfd);
				netfd = -1;
			}
		} else
			ast_log(LOG_WARNING, "Unable to create socket!\n");
	}
	if (netfd > -1)
		start_network_thread();
	else
		ast_log(LOG_WARNING, "BTP Disabled\n");
	ast_cli_register(&cli_show_btp);
	load_config();
	return 0;
}

int reload()
{
	load_config();
	return 0;
}

int unload_module()
{
	/* First, take us out of the channel loop */
	ast_cli_unregister(&cli_show_btp);
	ast_channel_unregister(&btptech);
	return 0;
}

int usecount()
{
	int res;
	ast_mutex_lock(&usecnt_lock);
	res = usecnt;
	ast_mutex_unlock(&usecnt_lock);
	return res;
}

char *key()
{
	return ASTERISK_GPL_KEY;
}

char *description()
{
	return desc;
}

