/*
 * Asterisk -- An open source telephony toolkit.
 *
 * Copyright (C) 1999 - 2006, Digium, Inc.
 *
 * Mark Spencer <markster@digium.com>
 *
 * See http://www.asterisk.org for more information about
 * the Asterisk project. Please do not directly contact
 * any of the maintainers of this project for assistance;
 * the project provides a web site, mailing lists and IRC
 * channels for your use.
 *
 * This program is free software, distributed under the terms of
 * the GNU General Public License Version 2. See the LICENSE file
 * at the top of the source tree.
 */

/*! \file
 *
 * \brief Bluetooth Cell / Mobile Phone channel driver
 * 
 * \author Dave Bowerman <david.bowerman@gmail.com>
 *
 * \ingroup channel_drivers
 */

/*** MODULEINFO
	<depend>bluetooth</depend>
 ***/

#include "asterisk.h"

ASTERISK_FILE_VERSION(__FILE__, "$Revision: 12 $")

#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <signal.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
#include <bluetooth/rfcomm.h>
#include <bluetooth/sco.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/utils.h"
#include "asterisk/linkedlists.h"
#include "asterisk/cli.h"
#include "asterisk/devicestate.h"
#include "asterisk/causes.h"
#include "asterisk/dsp.h"

#define CEL_CONFIG "cellphone.conf"

#ifdef HAS_ASTERISK_1_2
AST_MUTEX_DEFINE_STATIC(randomlock);
long int ast_random(void)
{
	long int res;
	ast_mutex_lock(&randomlock);
	res = random();
	ast_mutex_unlock(&randomlock);
	return res;
}
#endif

static int prefformat = AST_FORMAT_SLINEAR;
static char type[] = "CELL";

static int discovery_interval = 60;	/* The device discovery interval, default 60 seconds. */
static int sco_socket;			/* This is global so it can be closed on module unload outside of the listener thread */

enum cel_state {
	CEL_STATE_INIT = 0,
	CEL_STATE_INIT1,
	CEL_STATE_INIT2,
	CEL_STATE_INIT3,
	CEL_STATE_INIT4,
	CEL_STATE_INIT5,
	CEL_STATE_INIT6,
	CEL_STATE_PREIDLE,
	CEL_STATE_IDLE,
	CEL_STATE_DIAL,
	CEL_STATE_DIAL1,
	CEL_STATE_OUTGOING,
	CEL_STATE_RING,
	CEL_STATE_RING2,
	CEL_STATE_RING3,
	CEL_STATE_INCOMING,
	CEL_STATE_HANGUP,
	CEL_STATE_INSMS,
	CEL_STATE_OUTSMS,
	CEL_STATE_OUTSMS1,
	CEL_STATE_OUTSMS2
};

struct cel_pvt {
	struct ast_channel *owner;		/* Channel we belong to, possibly NULL */
	struct ast_frame fr;			/* "null" frame */
	char id[31];				/* The id from cellphone.conf */
	char bdaddr[18];			/* the bdaddr of the device */
	char context[AST_MAX_CONTEXT];		/* the context for incoming calls */
	char connected;				/* is it connected? */
	int rfcomm_port;			/* rfcomm port number */
	int rfcomm_socket;			/* rfcomm socket descriptor */
	char rfcomm_buf[256];
	int sco_socket;				/* sco socket descriptor */
	enum cel_state state;			/* monitor thread current state */
	pthread_t monitor_thread;		/* monitor thread handle */
	char sco_in_buf[48];
	char sco_out_buf[352];
	char *sco_out_ptr;
	int sco_out_len;
	char dial_number[AST_MAX_EXTENSION];	/* number for the monitor thread to dial */
	int dial_timeout;
	char ciev_call_0[4];			/* dynamically build reponse strings */
	char ciev_call_1[4];
	char ciev_callsetup_0[4];
	char ciev_callsetup_1[4];
	char ciev_callsetup_2[4];
	char ciev_callsetup_3[4];
	char no_callsetup;
	char has_sms;
	char sms_txt[161];
	struct ast_dsp *dsp;
	int dtmfrelax;
	int dsp_features;
	char sent_answer;
	char hangup_count;
	AST_LIST_ENTRY(cel_pvt) entry;
};

static AST_LIST_HEAD_STATIC(devices, cel_pvt);

/* The discovery thread */
static pthread_t discovery_thread = AST_PTHREADT_NULL;
/* The sco listener thread */
static pthread_t sco_listener_thread = AST_PTHREADT_NULL;

/* CLI stuff */
#ifdef HAS_ASTERISK_1_2
static char show_usage[] =
#else
static const char show_usage[] =
#endif
"Usage: cell show devices\n" 
"       Shows the state of Bluetooth Cell / Mobile devices.\n";

#ifdef HAS_ASTERISK_1_2
static char search_usage[] =
#else
static const char search_usage[] =
#endif
"Usage: cell search\n" 
"       Searches for Bluetooth Cell / Mobile devices in range.\n";

static int do_show_devices(int, int, char **);
static int do_search_devices(int, int, char **);

static struct ast_cli_entry cel_cli[] = {
	{{"cell", "show", "devices", NULL}, do_show_devices, "Show Bluetooth Cell / Mobile devices", show_usage},
	{{"cell", "search", NULL}, do_search_devices, "Search for Bluetooth Cell / Mobile devices", search_usage}
};

/* App stuff */
static char *app_celstatus = "CellStatus";
static char *celstatus_synopsis = "CellStatus(Device,Variable)";
static char *celstatus_desc =
"CellStatus(Device,Variable)\n"
"  Device - Id of cell phone from cellphone.conf\n"
"  Variable - Variable to store status in will be 1-3.\n" 
"             In order, Disconnected, Connected & Free, Connected & Busy.\n";

static char *app_celsendsms = "CellSendSMS";
static char *celsendsms_synopsis = "CellSendSMS(Device,Dest,Message)";
static char *celsendsms_desc =
"CellSendSms(Device,Dest,Message)\n"
"  Device - Id of cell phone from cellphone.conf\n"
"  Dest - destination\n"
"  Message - text of the message\n";

static struct ast_channel *cel_request(const char *type, int format, void *data, int *cause);
static int cel_call(struct ast_channel *ast, char *dest, int timeout);
static int cel_hangup(struct ast_channel *ast);
static int cel_answer(struct ast_channel *ast);
#ifdef HAS_ASTERISK_1_2
static int cel_digit_end(struct ast_channel *c, char digit);
#else
static int cel_digit_begin(struct ast_channel *ast, char digit);
static int cel_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
#endif
static struct ast_frame *cel_read(struct ast_channel *ast);
static int cel_write(struct ast_channel *ast, struct ast_frame *frame);
static int cel_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
static int cel_devicestate(void *data);
static struct ast_channel *cel_new(int state, struct cel_pvt *pvt, char *cid_num);

static int rfcomm_write(struct cel_pvt *pvt, char *buf);
static int rfcomm_read(struct cel_pvt *pvt, char *buf, char flush, int timeout);
int sco_flush(struct cel_pvt *pvt);
static int sdp_search(char *addr);

static const struct ast_channel_tech cel_tech = {
	.type = type,
	.description = "Bluetooth Cellphone Driver",
	.capabilities = AST_FORMAT_SLINEAR,
	.requester = cel_request,
	.call = cel_call,
	.hangup = cel_hangup,
	.answer = cel_answer,
#if HAS_ASTERISK_1_2
	.send_digit = cel_digit_end,
#else
	.send_digit_begin = cel_digit_begin,
	.send_digit_end = cel_digit_end,
#endif
	.read = cel_read,
	.write = cel_write,
	.fixup = cel_fixup,
	.devicestate = cel_devicestate
};

static int do_show_devices(int fd, int argc, char **argv)
{

	struct cel_pvt *pvt;

	#define FORMAT "%-15.15s %-17.17s %-9.9s %-5.5s %-3.3s\n"

	ast_cli(fd, FORMAT, "ID", "Address", "Connected", "State", "SMS");
	AST_LIST_TRAVERSE(&devices, pvt, entry) {
		ast_cli(fd, FORMAT, pvt->id, pvt->bdaddr, pvt->connected?"Yes":"No", (pvt->state == CEL_STATE_IDLE)?"Free":(pvt->state < CEL_STATE_IDLE)?"Init":"Busy", (pvt->has_sms)?"Yes":"No");
	}	

	return RESULT_SUCCESS;

}

static int do_search_devices(int fd, int argc, char **argv)
{

	int hci_socket;
	inquiry_info *ii = NULL;
	int max_rsp, num_rsp;
	int dev_id, len, flags;
	int i, port;
	char addr[19] = {0};
	char name[31] = {0};

	#define FORMAT2 "%-17.17s %-30.30s %-6.6s %-4.4s\n"
	#define FORMAT3 "%-17.17s %-30.30s %-6.6s %d\n"

	dev_id = hci_get_route(NULL);
	hci_socket = hci_open_dev(dev_id);
	len  = 8;
	max_rsp = 255;
	flags = IREQ_CACHE_FLUSH;

	ii = (inquiry_info*)malloc(max_rsp * sizeof(inquiry_info));
	num_rsp = hci_inquiry(dev_id, len, max_rsp, NULL, &ii, flags);
	if (num_rsp > 0) {
		ast_cli(fd, FORMAT2, "Address", "Name", "Usable", "Port");
		for (i = 0; i < num_rsp; i++) {
			ba2str(&(ii+i)->bdaddr, addr);
			name[0] = 0x00;
			if (hci_read_remote_name(hci_socket, &(ii+i)->bdaddr, sizeof(name)-1, name, 0) < 0)
				strcpy(name, "[unknown]");
			port = sdp_search(addr);
			ast_cli(fd, FORMAT3, addr, name, port?"Yes":"No", port);
		}
	} else
		ast_cli(fd, "No Bluetooth Cell / Mobile devices found.\n");

	free(ii);

	hci_close_dev(hci_socket);

	return RESULT_SUCCESS;

}

static int cel_status_exec(struct ast_channel *ast, void *data)
{

	struct cel_pvt *pvt;
	char *args = NULL, *device = NULL, *variable = NULL;
	int stat;
	char status[2];

	if (!data)
		return -1;

	args = ast_strdupa((char *)data);
	device = strsep(&args, "|");
	if (device && (device[0] != 0x00)) {
		variable = args;
	} else
		return -1;

	stat = 1;

	AST_LIST_TRAVERSE(&devices, pvt, entry) {
		if (!strcmp(pvt->id, device))
			break;
	}

	if (pvt) {
		if (pvt->connected)
			stat = 2;
		if (pvt->owner)
			stat = 3;
	}

	sprintf(status, "%d", stat);
	pbx_builtin_setvar_helper(ast, variable, status);

	return 0;

}

static int cel_sendsms_exec(struct ast_channel *ast, void *data)
{

	struct cel_pvt *pvt;
	char *args = NULL, *device = NULL, *dest = NULL, *message = NULL;

	if (!data)
		return -1;

	args = ast_strdupa((char *)data);
	device = strsep(&args, "|");
	if (device && (device[0] != 0x00)) {
		dest = strsep(&args, "|");
		if (dest && (dest[0] != 0x00)) {
			message = args;
			if (!message || (message[0] == 0x00))
				return -1;
		} else
			return -1;
	} else
		return -1;

	AST_LIST_TRAVERSE(&devices, pvt, entry) {
		if (!strcmp(pvt->id, device))
			break;
	}

	if (!pvt)
		return -1;
	if (!pvt->connected)
		return -1;
	if (!pvt->has_sms)
		return -1;
	if (pvt->state != CEL_STATE_IDLE)
		return -1;

	strcpy(pvt->dial_number, dest);
	memset(pvt->sms_txt, 0x0, sizeof(pvt->sms_txt));
	strncpy(pvt->sms_txt, message, 160);
	pvt->state = CEL_STATE_OUTSMS;

	return 0;

}

static struct ast_channel *cel_request(const char *type, int format, void *data, int *cause)
{

	struct ast_channel *chn = NULL;
	struct cel_pvt *pvt;
	char *dest_dev = NULL;
	char *dest_num = NULL;
	int oldformat;

	if (!data) {
		ast_log(LOG_WARNING, "Channel requested with no data\n");
		*cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
		return NULL;
	}

	oldformat = format;
	format &= (AST_FORMAT_SLINEAR);
	if (!format) {
		ast_log(LOG_WARNING, "Asked to get a channel of unsupported format '%d'\n", oldformat);
		*cause = AST_CAUSE_FACILITY_NOT_IMPLEMENTED;
		return NULL;
	}

	dest_dev = ast_strdupa((char *)data);
	dest_num = strchr(dest_dev, '/');
	if (!dest_num) {
		ast_log(LOG_WARNING, "Cant determine destination number.\n");
		*cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
		return NULL;
	}
	*dest_num++ = 0x00;

	/* Find requested device and make sure its connected. */
	AST_LIST_TRAVERSE(&devices, pvt, entry) {
		if (!strcmp(pvt->id, dest_dev)) {
			break;
		}
	}
	if (!pvt || !pvt->connected || pvt->owner) {
		ast_log(LOG_WARNING, "Request to call on device %s which is not connected / already in use.\n", dest_dev);
		*cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
		return NULL;
	}

	pvt->sco_out_ptr = pvt->sco_out_buf;
	pvt->sco_out_len = 0;
	chn = cel_new(AST_STATE_DOWN, pvt, NULL);
	if (!chn) {
		ast_log(LOG_WARNING, "Unable to allocate channel structure\n");
		*cause = AST_CAUSE_REQUESTED_CHAN_UNAVAIL;
		return NULL;
	}

	return chn;

}

static int cel_call(struct ast_channel *ast, char *dest, int timeout)
{

	struct cel_pvt *pvt;
	char *dest_dev = NULL;
	char *dest_num = NULL;

	dest_dev = ast_strdupa((char *)dest);
	dest_num = strchr(dest_dev, '/');
	if (!dest_num) {
		ast_log(LOG_WARNING, "Cant determine destination number.\n");
		return -1;
	}
	*dest_num++ = 0x00;

	pvt = ast->tech_pvt;

	if ((ast->_state != AST_STATE_DOWN) && (ast->_state != AST_STATE_RESERVED)) {
		ast_log(LOG_WARNING, "cel_call called on %s, neither down nor reserved\n", ast->name);
		return -1;
	}

	if (option_debug)
		ast_log(LOG_DEBUG, "Calling %s on %s\n", dest, ast->name);

	ast_copy_string(pvt->dial_number, dest_num, sizeof(pvt->dial_number));
	pvt->state = CEL_STATE_DIAL;
	pvt->dial_timeout = timeout == 0 ? 30 : timeout;

	return 0;

}

static int cel_hangup(struct ast_channel *ast)
{

	struct cel_pvt *pvt;

	if (!ast->tech_pvt) {
		ast_log(LOG_WARNING, "Asked to hangup channel not connected\n");
		return 0;
	}
	pvt = ast->tech_pvt;

	if (option_debug)
		ast_log(LOG_DEBUG, "Hanging up device %s.\n", pvt->id);

#ifdef HAS_ASTERISK_1_2
	ast_mutex_lock(&ast->lock);
#else
	ast_channel_lock(ast);
#endif
	ast->fds[0] = -1;
#ifdef HAS_ASTERISK_1_2
	ast_mutex_unlock(&ast->lock);
#else
	ast_channel_unlock(ast);
#endif

	if (pvt->state == CEL_STATE_INCOMING || pvt->state == CEL_STATE_OUTGOING || pvt->state == CEL_STATE_DIAL1) {
		sco_flush(pvt);
		rfcomm_write(pvt, "AT+CHUP\r");
		pvt->state = CEL_STATE_HANGUP;
		pvt->hangup_count = 0;
	} else
		pvt->state = CEL_STATE_IDLE;

	pvt->owner = NULL;
	ast->tech_pvt = NULL;
	ast_setstate(ast, AST_STATE_DOWN);

	return 0;

}

static int cel_answer(struct ast_channel *ast)
{

	struct cel_pvt *pvt;

	pvt = ast->tech_pvt;

	rfcomm_write(pvt, "ATA\r");

	ast_setstate(ast, AST_STATE_UP);

	pvt->sent_answer = 1;

	return 0;

}

#ifndef HAS_ASTERISK_1_2
static int cel_digit_begin(struct ast_channel *chan, char digit)
{

	return 0;

}
#endif

#ifdef HAS_ASTERISK_1_2
static int cel_digit_end(struct ast_channel *ast, char digit)
#else
static int cel_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
#endif
{

	struct cel_pvt *pvt;
	char buf[11];
	
	pvt = ast->tech_pvt;

	if (option_debug)
		ast_log(LOG_DEBUG, "Dialed %c\n", digit);

	switch(digit) {
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
	case '8':
	case '9':
	case '*':
	case '#':
		sprintf(buf, "AT+VTS=%c\r", digit);
		rfcomm_write(pvt, buf);
		break;
	default:
		ast_log(LOG_WARNING, "Unknown digit '%c'\n", digit);
		return -1;
	}

	return 0;

}

/*

	The SCO protocol basically delivers audio in 48 byte 'frames' in slin format.
	Here we just package these into an ast_frame and return them.
	The SCO connection from the device to Asterisk happens asynchronously, so it is feasible
	that Asterisk will call cel_read() before the device has connected. In that case we just return
	a null frame.

*/

static struct ast_frame *cel_read(struct ast_channel *ast)
{

	struct cel_pvt *pvt = ast->tech_pvt;
	struct ast_frame *f;
	int r;

	if (!pvt->owner)
#ifdef HAS_ASTERISK_1_2
		return NULL;
#else
		return &ast_null_frame;
#endif

	if (pvt->state == CEL_STATE_HANGUP)
#ifdef HAS_ASTERISK_1_2
		return NULL;
#else
		return &ast_null_frame;
#endif

	if (pvt->sco_socket == -1)
#ifdef HAS_ASTERISK_1_2
		return NULL;
#else
		return &ast_null_frame;
#endif

	pvt->fr.frametype = AST_FRAME_VOICE;
	pvt->fr.subclass = AST_FORMAT_SLINEAR;
	pvt->fr.datalen = 48;
	pvt->fr.samples = 24;
	pvt->fr.src = type;
	pvt->fr.offset = 0;
	pvt->fr.mallocd = 0;
	pvt->fr.delivery.tv_sec = 0;
	pvt->fr.delivery.tv_usec = 0;
	pvt->fr.data = pvt->sco_in_buf;

	if ((r = read(pvt->sco_socket, pvt->sco_in_buf, 48)) == 48) {
		f = ast_dsp_process(ast, pvt->dsp, &pvt->fr);
		if (f && (f->frametype == AST_FRAME_DTMF)) {
			memcpy(&pvt->fr, f, sizeof(struct ast_frame));
		}
		return &pvt->fr;
	} else if (r == -1) {
		if (errno != EAGAIN) {
			close(pvt->sco_socket);
			pvt->sco_socket = -1;
			if (pvt->owner) {
#ifdef HAS_ASTERISK_1_2
				ast_mutex_lock(&pvt->owner->lock);
#else
				ast_channel_lock(pvt->owner);
#endif
				pvt->owner->fds[0] = -1;
#ifdef HAS_ASTERISK_1_2
				ast_mutex_unlock(&pvt->owner->lock);
#else
				ast_channel_unlock(pvt->owner);
#endif
			}
		}
	} else {
		if (option_debug)
			ast_log(LOG_DEBUG, "cel_read() read short frame. (%d)\n", r);
	}

#ifdef HAS_ASTERISK_1_2
	return NULL;
#else
	return &ast_null_frame;
#endif
}

/*

	We need to deliver 48 byte 'frames' of slin format audio to the device.
	cel_write() handles this by buffering short frames until the next time we are called.

*/

static int cel_write(struct ast_channel *ast, struct ast_frame *frame)
{

	struct cel_pvt *pvt = ast->tech_pvt;
	int num_frames, i, r;
	char *pfr;

	if (frame->frametype != AST_FRAME_VOICE)
		return 0;

	if (pvt->sco_socket == -1)
		return 0;

	if (pvt->state == CEL_STATE_HANGUP)
		return 0;

	if (option_debug) {
		if (frame->datalen > sizeof(pvt->sco_out_buf) - pvt->sco_out_len)
			ast_log(LOG_DEBUG, "Overrun on sco_out_buf detected.\n");
	}
	memmove(pvt->sco_out_ptr, frame->data, frame->datalen);
	pvt->sco_out_len += frame->datalen;
	num_frames = pvt->sco_out_len / 48;

	pfr = pvt->sco_out_buf;
	for (i=0; i<num_frames; i++) {
		if ((r = write(pvt->sco_socket, pfr, 48)) == -1) {
			close(pvt->sco_socket); /* just for luck */
			pvt->sco_socket = -1;
		}
		pfr += 48;
	}

	pvt->sco_out_len = pvt->sco_out_len - (num_frames * 48);
	memmove(pvt->sco_out_buf, pfr, pvt->sco_out_len);
	pvt->sco_out_ptr = pvt->sco_out_buf + pvt->sco_out_len; 

	return 0;

}

static int cel_fixup(struct ast_channel *oldchan, struct ast_channel *newchan)
{

	struct cel_pvt *pvt = oldchan->tech_pvt;

	if (pvt && pvt->owner == oldchan)
		pvt->owner = newchan;

	return 0;

}

static int cel_devicestate(void *data)
{

	char *device;
	int res = AST_DEVICE_INVALID;
	struct cel_pvt *pvt;

	device = ast_strdupa(data ? data : "");

	if (option_debug)
		ast_log(LOG_DEBUG, "Checking device state for device %s\n", device);

	AST_LIST_TRAVERSE(&devices, pvt, entry) {
		if (!strcmp(pvt->id, device))
			break;
	}

	if (pvt) {
		if (pvt->connected) {
			if (pvt->owner)
				res = AST_DEVICE_INUSE;
			else
				res = AST_DEVICE_NOT_INUSE;
		}
	}

	return res;

}

static struct ast_channel *cel_new(int state, struct cel_pvt *pvt, char *cid_num)
{

	struct ast_channel *chn;

#ifdef HAS_ASTERISK_1_2
	chn = ast_channel_alloc(1);
#else
	chn = ast_channel_alloc(1, state, 0, 0, "CELL/%s-%04lx", pvt->id, ast_random() & 0xffff);
#endif
	if (chn) {
#ifdef HAS_ASTERISK_1_2
		snprintf(chn->name, sizeof(chn->name) - 1, "CELL/%s-%04lx", pvt->id, ast_random() & 0xffff);
		chn->_state = state;
		chn->type = "CELL";
#endif

		chn->tech = &cel_tech;
		chn->nativeformats = prefformat;
		chn->rawreadformat = prefformat;
		chn->rawwriteformat = prefformat;
		chn->writeformat = prefformat;
		chn->readformat = prefformat;
		chn->tech_pvt = pvt;
		if (state == AST_STATE_RING)
			chn->rings = 1;
		if (pvt->sco_socket != -1)
			chn->fds[0] = pvt->sco_socket;
		ast_copy_string(chn->context, pvt->context, sizeof(chn->context));
		ast_copy_string(chn->exten, "s", sizeof(chn->exten));
#ifndef HAS_ASTERISK_1_2
		ast_string_field_set(chn, language, "en");
#endif
		if (cid_num)
#ifdef HAS_ASTERISK_1_2
			chn->cid.cid_num = ast_strdupa(cid_num);
		chn->cid.cid_name = ast_strdupa(pvt->id);
#else
			chn->cid.cid_num = ast_strdup(cid_num);
		chn->cid.cid_name = ast_strdup(pvt->id);
#endif
		pvt->owner = chn;
	}

	return chn;

}

static int rfcomm_connect(char *bdaddr, int remote_channel) {

	bdaddr_t dst;
	struct sockaddr_rc addr;
	int s;

	str2ba(bdaddr, &dst);

	if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) {
		if (option_debug)
			ast_log(LOG_DEBUG, "socket() failed (%d).\n", errno);
		return -1;
	}

	memset(&addr, 0, sizeof(addr));
	addr.rc_family = AF_BLUETOOTH;
	bacpy(&addr.rc_bdaddr, &dst);
	addr.rc_channel = remote_channel;
	if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		if (option_debug)
			ast_log(LOG_DEBUG, "connect() failed (%d).\n", errno);
		close(s);
		return -1;
	}

	return s;

}

static int rfcomm_write(struct cel_pvt *pvt, char *buf)
{

	char *p;
	ssize_t num_write;
	int len;

	if (option_debug)
		ast_log(LOG_DEBUG, "rfcomm_write() (%s) [%s]\n", pvt->id, buf);
	len = strlen(buf);
	p = buf;
	while (len > 0) {
		if ((num_write = write(pvt->rfcomm_socket, p, len)) == -1) {
			ast_log(LOG_DEBUG, "rfcomm_write() error [%d]\n", errno);
			return 0;
		}
		len -= num_write;
		p += num_write;
	}

	return 1;

}

/*

	Here we need to return complete '\r' terminated single responses to the devices monitor thread, or
	a timeout if nothing is available.
	The rfcomm connection to the device is asynchronous, so there is no guarantee that responses will
	be returned in a single read() call. We handle this by buffering the input and returning one response
	per call, or a timeout if nothing is available.

*/

static int rfcomm_read(struct cel_pvt *pvt, char *buf, char flush, int timeout)
{

	int sel, rlen, slen;
	fd_set rfds;
	struct timeval tv;
	char *p;

	if (!flush) {
		if ((p = strchr(pvt->rfcomm_buf, '\n'))) {
			*p++ = 0x00;
			memmove(buf, pvt->rfcomm_buf, strlen(pvt->rfcomm_buf));
			*(buf + strlen(pvt->rfcomm_buf)) = 0x00;
			memmove(pvt->rfcomm_buf, p, strlen(p));
			*(pvt->rfcomm_buf+strlen(p)) = 0x00;
			return 1;
		}
	} else {
		pvt->rfcomm_buf[0] = 0x00;
	}

	FD_ZERO(&rfds);
	FD_SET(pvt->rfcomm_socket, &rfds);

	tv.tv_sec = timeout;
	tv.tv_usec = 0;

	if ((sel = select(pvt->rfcomm_socket + 1, &rfds, NULL, NULL, &tv)) > 0) {
		if (FD_ISSET(pvt->rfcomm_socket, &rfds)) {
			slen = strlen(pvt->rfcomm_buf);
			rlen = read(pvt->rfcomm_socket, pvt->rfcomm_buf + slen, sizeof(pvt->rfcomm_buf) - slen - 1);
			if (rlen > 0) {
				pvt->rfcomm_buf[slen+rlen] = 0x00;
				if ((p = strchr(pvt->rfcomm_buf, '\n'))) {
					*p++ = 0x00;
					memmove(buf, pvt->rfcomm_buf, strlen(pvt->rfcomm_buf));
					*(buf + strlen(pvt->rfcomm_buf)) = 0x00;
					memmove(pvt->rfcomm_buf, p, strlen(p));
					*(pvt->rfcomm_buf+strlen(p)) = 0x00;
					return 1;
				}
			} else
				return rlen;
		}
	} else if (sel == 0) { /* timeout */
		return 0;
	}

	return 1;

}

/*

	Some devices wont respond to AT+CHUP on the rfcomm socket if there is data
	waiting to be read on the sco socket. This function consumes all the sco
	audio, so we have a chance to get the AT+CHUP in...

*/

int sco_flush(struct cel_pvt *pvt)
{

	char buf[48];

	errno = 0;
	while ((read(pvt->sco_socket, buf, 48) != -1) && (errno != EAGAIN)) {}

	return 1;

}

/*

	sdp_search() performs a service discovery on the given device to determine whether
	or not it supports the Handsfree Profile.

*/

static int sdp_search(char *addr)
{

	sdp_session_t *session = 0;
	bdaddr_t bdaddr;
	uuid_t svc_uuid;
	uint32_t range = 0x0000ffff;
	sdp_list_t *response_list, *search_list, *attrid_list;
	int status, port;
	sdp_list_t *proto_list;
	sdp_record_t *sdprec;

	str2ba(addr, &bdaddr);
	port = 0;
	session = sdp_connect(BDADDR_ANY, &bdaddr, SDP_RETRY_IF_BUSY);
	if (!session) {
		if (option_debug)
			ast_log(LOG_DEBUG, "sdp_connect() failed on device %s.\n", addr);
		return 0;
	}

	sdp_uuid32_create(&svc_uuid, HANDSFREE_AGW_PROFILE_ID);
	search_list = sdp_list_append(0, &svc_uuid);
	attrid_list = sdp_list_append(0, &range);
	response_list = 0x00;
	status = sdp_service_search_attr_req(session, search_list, SDP_ATTR_REQ_RANGE, attrid_list, &response_list);
	if (status == 0) {
		if (response_list) {
			sdprec = (sdp_record_t *) response_list->data;
			proto_list = 0x00;
			if (sdp_get_access_protos(sdprec, &proto_list) == 0) {
				port = sdp_get_proto_port(proto_list, RFCOMM_UUID);
				sdp_list_free(proto_list, 0);
			}
			sdp_record_free(sdprec);
			sdp_list_free(response_list, 0);
		} else {
			if (option_debug)
				ast_log(LOG_DEBUG, "No responses returned for device %s.\n", addr);
		}
	} else {
		if (option_debug)
			ast_log(LOG_DEBUG, "sdp_service_search_attr_req() failed on device %s.\n", addr);
	}

	sdp_list_free(search_list, 0);
	sdp_list_free(attrid_list, 0);
	sdp_close(session);

	return port;

}

/*

	Monitor Thread

	This thread is spun once a device is discovered and considered capable of being used, i.e. supports Handsfree Profile,
	and its configured in cellphone.conf.
	The thread lives for the lifetime of the bluetooth connection, and handles the 'control' side of all calls.

*/

static void *do_monitor(void *data)
{

	struct cel_pvt *pvt = (struct cel_pvt *)data;
	struct ast_channel *chn;
	char monitor = 1;
	char buf[256];
	char cid_num[AST_MAX_EXTENSION], *pcids, *pcide;
	int s, t, i, smsi;
	int group, group2;
	int callp = 0, callsetupp;
	char brsf, nsmode, *p, *p1;
	char sms_src[13];
	char sms_txt[161];

	brsf = nsmode = 0;

	if (!rfcomm_write(pvt, "AT+BRSF=4\r"))
		monitor = 0;

	while (monitor) {

		if (pvt->state == CEL_STATE_DIAL1)
			t = pvt->dial_timeout;
		else if (pvt->state == CEL_STATE_HANGUP)
			t = 2;
		else if (pvt->state == CEL_STATE_OUTSMS1)
			t = 2;
		else if (pvt->state == CEL_STATE_OUTSMS2)
			t = 10;
		else
			t = 1;

		s = rfcomm_read(pvt, buf, 0, t);

		if ((s > 0) && (buf[0] != 0x0) && (buf[0] != '\r')) {
			if (option_debug)
				ast_log(LOG_DEBUG, "rfcomm_read() (%s) [%s]\n", pvt->id, buf);
			switch (pvt->state) {
			case 0:
				if (strstr(buf, "+BRSF:")) {
					brsf = 1;
				} else if (strstr(buf, "ERROR\r") && !nsmode) { /* Hmmm, Non-Standard Phone, just continue */
					rfcomm_write(pvt, "AT+CIND=?\r");
					pvt->state++;
					nsmode = 1;
				} else if (strstr(buf, "OK\r") && brsf) {
					rfcomm_write(pvt, "AT+CIND=?\r");
					pvt->state++;
				}
				break;
			case 1:
				if (strstr(buf, "+CIND:")) {
					group = callp = callsetupp = 0;
					group2 = 1;
					for (i=0; i<strlen(buf); i++) {
						if (buf[i] == '(')
							group++;
						if (buf[i] == ')') {
							group--;
							if (group == 0)
								group2++;
						}
						if (strstr(buf+i, "\"call\""))
							callp = group2;	
						if (strstr(buf+i, "\"call_setup\""))
							callsetupp = group2;
						if (strstr(buf+i, "\"callsetup\""))
							callsetupp = group2;
					}
					sprintf(pvt->ciev_call_0, "%d,0", callp);
					sprintf(pvt->ciev_call_1, "%d,1", callp);
					sprintf(pvt->ciev_callsetup_0, "%d,0", callsetupp);
					sprintf(pvt->ciev_callsetup_1, "%d,1", callsetupp);
					sprintf(pvt->ciev_callsetup_2, "%d,2", callsetupp);
					sprintf(pvt->ciev_callsetup_3, "%d,3", callsetupp);
					if (callsetupp == 0) /* This phone has no call setup indication!! ... */
						pvt->no_callsetup = 1;
					if (option_debug)
						ast_log(LOG_DEBUG, "CIEV_CALL=%d CIEV_CALLSETUP=%d\n", callp, callsetupp);
				}
				if (strstr(buf, "OK\r")) {
					rfcomm_write(pvt, "AT+CIND?\r");
					pvt->state++;
				}
				break;
			case 2:
				if ((p = strstr(buf, "+CIND:"))) {
					p += 5;
					if (*(p+(callp*2)) == '1') {
						ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s has a call in progress - delaying connection.\n", pvt->id);
						monitor = 0;
					}
				} else if (strstr(buf, "OK\r")) {
					rfcomm_write(pvt, "AT+CMER=3,0,0,1\r");
					pvt->state++;
				}
				break;
			case 3:
				if (strstr(buf, "OK\r")) {
					rfcomm_write(pvt, "AT+CLIP=1\r");
					pvt->state++;
				}
				break;
			case 4:
				if (strstr(buf, "OK\r")) {
					rfcomm_write(pvt, "AT+CMGF=1\r");
					pvt->state++;
				}
				break;
			case 5:
				if (strstr(buf, "ERROR\r")) { /* No SMS Support ! */
					pvt->state = CEL_STATE_PREIDLE;
				} else if (strstr(buf, "OK\r")) {
					rfcomm_write(pvt, "AT+CNMI=2,1,0,1,0\r");
					pvt->state++;
				}
				break;
			case 6:
				if (strstr(buf, "OK\r")) { /* We have SMS Support */
					pvt->has_sms = 1;
					pvt->state = CEL_STATE_PREIDLE;
				} else if (strstr(buf, "ERROR\r")) {
					pvt->has_sms = 0;
					pvt->state = CEL_STATE_PREIDLE;
				}
				break;
			case CEL_STATE_PREIDLE: /* Nothing handled here, wait for timeout, then off we go... */
				break;
			case CEL_STATE_IDLE:
				if (option_debug)
					ast_log(LOG_DEBUG, "Device %s %s [%s]\n", pvt->bdaddr, pvt->id, buf);
				if (strstr(buf, "RING\r")) {
					pvt->state = CEL_STATE_RING;
				} else if (strstr(buf, "+CIEV:")) {
					if (strstr(buf, pvt->ciev_callsetup_3)) {	/* User has dialed out on handset */
						monitor = 0;				/* We disconnect now, as he is    */
					}						/* probably leaving BT range...   */
				}
				break;
			case CEL_STATE_DIAL: /* Nothing handled here, we need to wait for a timeout */
				break;
			case CEL_STATE_DIAL1:
				if (strstr(buf, "OK\r")) {
					if (pvt->no_callsetup) {
						ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
					} else {
						ast_setstate(pvt->owner, AST_STATE_RINGING);
					}
					pvt->state = CEL_STATE_OUTGOING;
				}
				break;
			case CEL_STATE_OUTGOING:
				if (strstr(buf, "+CIEV")) {
					if (strstr(buf, pvt->ciev_call_0)) { 				/* call was hung up */
						ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
					} else if (strstr(buf, pvt->ciev_callsetup_3)) { 		/* b-party ringing */
						ast_queue_control(pvt->owner, AST_CONTROL_RINGING);
					} else if (strstr(buf, pvt->ciev_call_1) && !pvt->no_callsetup) { /* b-party answer */
						ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
					}
				}
				break;
			case CEL_STATE_RING:
				cid_num[0] = 0x00;
				if ((pcids = strstr(buf, "+CLIP:"))) {
					if ((pcids = strchr(pcids, '"'))) {
						if ((pcide = strchr(pcids+1, '"'))) {
							strncpy(cid_num, pcids+1, pcide - pcids - 1);
							cid_num[pcide - pcids - 1] = 0x00;
						}
					}
					pvt->state = CEL_STATE_RING2;
				}
				break;
			case CEL_STATE_RING2:
				pvt->sco_out_ptr = pvt->sco_out_buf;
				pvt->sco_out_len = 0;
				pvt->sent_answer = 0;
				chn = cel_new(AST_STATE_RING, pvt, cid_num);
				if (chn) {
					if (ast_pbx_start(chn)) {
						ast_log(LOG_ERROR, "Unable to start pbx on incoming call.\n");
						ast_hangup(chn);
					} else
						pvt->state = CEL_STATE_RING3;
				} else {
					ast_log(LOG_ERROR, "Unable to allocate channel for incoming call.\n");
					rfcomm_write(pvt, "AT+CHUP\r");
					pvt->state = CEL_STATE_IDLE;
				}
				break;
			case CEL_STATE_RING3:
				if (strstr(buf, "+CIEV")) {
					if (strstr(buf, pvt->ciev_call_1)) {
						if (pvt->sent_answer) {	/* We answered */
							pvt->state = CEL_STATE_INCOMING;
						} else {		/* User answered on handset!, disconnect */
							pvt->state = CEL_STATE_IDLE;
							if (pvt->sco_socket > -1)
								close(pvt->sco_socket);
							ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
						}
					}
					if ((strstr(buf, pvt->ciev_callsetup_0) || strstr(buf, pvt->ciev_call_0))) { /* Caller disconnected */
						ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
					}
				} 
				break;
			case CEL_STATE_INCOMING:
				if (strstr(buf, "+CIEV")) {
					if (strstr(buf, pvt->ciev_call_0)) {
						ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
					}
				}
				break;
			case CEL_STATE_HANGUP:
				if (strstr(buf, "OK\r") || strstr(buf, pvt->ciev_call_0)) {
					pvt->state = CEL_STATE_IDLE;
				}
				break;
			case CEL_STATE_INSMS:
				if (strstr(buf, "+CMGR:")) {
					if ((p = strchr(buf, ','))) {
						if (*(++p) == '"')
							p++;
						if ((p1 = strchr(p, ','))) {
							if (*(--p1) == '"')
								p1--;
							memset(sms_src, 0x00, sizeof(sms_src));
							strncpy(sms_src, p, p1 - p + 1);
						}
					}
				} else if (strstr(buf, "OK\r")) {
					chn = cel_new(AST_STATE_DOWN, pvt, NULL);
					strcpy(chn->exten, "sms");
					pbx_builtin_setvar_helper(chn, "SMSSRC", sms_src);
					pbx_builtin_setvar_helper(chn, "SMSTXT", sms_txt);
					if (ast_pbx_start(chn))
						ast_log(LOG_ERROR, "Unable to start pbx on incoming sms.\n");
					pvt->state = CEL_STATE_IDLE;
				} else {
					memset(sms_txt, 0x00, sizeof(sms_txt));
					strncpy(sms_txt, buf, strlen(buf) - 1);
				}
				break;
			case CEL_STATE_OUTSMS:
				break;
			case CEL_STATE_OUTSMS1:
				break;
			case CEL_STATE_OUTSMS2:
				if (strstr(buf, "OK\r")) {
					pvt->state = CEL_STATE_IDLE;
				}
				break;
			}
			/* Unsolicited responses */

 			if (strstr(buf, "+CMTI:")) {	/* SMS Incoming... */
				if ((p = strchr(buf, ','))) {
					p++;
					if ((p1 = strchr(p, '\r'))) {
						*p1 = 0x0;
						smsi = atoi(p);
						if (smsi > 0) {
							sprintf(buf, "AT+CMGR=%d\r", smsi);
							rfcomm_write(pvt, buf);
							pvt->state = CEL_STATE_INSMS;
						}
					}
				}
			}

		} else if (s == 0) { /* Timeouts */
			if (pvt->state == 2) { /* Some devices dont respond to AT+CIND? properly. RIM Blackberry 4 example */
				pvt->state++;
				rfcomm_write(pvt, "AT+CMER=3,0,0,1\r");
 			} else if (pvt->state == 3) { /* Some devices dont respond to AT+CMER=3,0,0,1 properly. VK 2020 for example */
                                 pvt->state++;
                                 rfcomm_write(pvt, "AT+CLIP=1\r");
			} else if (pvt->state == CEL_STATE_PREIDLE) {
				pvt->connected = 1;
				ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s initialised and ready.\n", pvt->id);
				pvt->state = CEL_STATE_IDLE;
			} else if (pvt->state == CEL_STATE_DIAL) {
				sprintf(buf, "ATD%s;\r", pvt->dial_number);
				if (!rfcomm_write(pvt, buf)) {
					ast_log(LOG_ERROR, "Dial failed on %s state %d\n", pvt->owner->name, pvt->state);
					ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION);
					pvt->state = CEL_STATE_IDLE;
				} else {
					pvt->state = CEL_STATE_DIAL1;
				}
			} else if (pvt->state == CEL_STATE_DIAL1) {
				ast_log(LOG_ERROR, "Dial failed on %s state %d\n", pvt->owner->name, pvt->state);
				ast_queue_control(pvt->owner, AST_CONTROL_CONGESTION);
				ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
				pvt->state = CEL_STATE_IDLE;
			} else if (pvt->state == CEL_STATE_RING) { /* No CLIP?, bump it */
				pvt->state = CEL_STATE_RING2;
			} else if (pvt->state == CEL_STATE_HANGUP) {
				if (pvt->hangup_count == 6) {
					if (option_debug)
						ast_log(LOG_DEBUG, "Device %s failed to hangup after 6 tries, disconnecting.\n", pvt->id);
					monitor = 0;
				}
				rfcomm_write(pvt, "AT+CHUP\r");
				pvt->hangup_count++;
			} else if (pvt->state == CEL_STATE_OUTSMS) {
				sprintf(buf, "AT+CMGS=\"%s\"\r", pvt->dial_number);
				rfcomm_write(pvt, buf);
				pvt->state = CEL_STATE_OUTSMS1;
			} else if (pvt->state == CEL_STATE_OUTSMS1) {
				if (pvt->rfcomm_buf[0] == '>') {
					sprintf(buf, "%s%c", pvt->sms_txt, 0x1a);
					rfcomm_write(pvt, buf);
					pvt->state = CEL_STATE_OUTSMS2;
				} else {
					ast_log(LOG_ERROR, "Failed to send SMS to %s on device %s\n", pvt->dial_number, pvt->id);
					pvt->state = CEL_STATE_IDLE;
				}
			} else if (pvt->state == CEL_STATE_OUTSMS2) {
				ast_log(LOG_ERROR, "Failed to send SMS to %s on device %s\n", pvt->dial_number, pvt->id);
				pvt->state = CEL_STATE_IDLE;
			}
		} else if (s == -1) {
			if (option_verbose > 2)
				ast_verbose(VERBOSE_PREFIX_3  "Bluetooth Device %s has disconnected, reason (%d).\n", pvt->id, errno); 
			monitor = 0;
		}

	}

	close(pvt->rfcomm_socket);
	close(pvt->sco_socket);
	pvt->sco_socket = -1;
	pvt->connected = 0;
	pvt->monitor_thread = AST_PTHREADT_NULL;

	return NULL;

}

static int start_monitor(struct cel_pvt *pvt)
{

#ifdef HAS_ASTERISK_1_2
	if (ast_pthread_create(&pvt->monitor_thread, NULL, do_monitor, pvt) < 0) {
#else
	if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor, pvt) < 0) {
#endif
		pvt->monitor_thread = AST_PTHREADT_NULL;
		return 0;
	}

	return 1;

}

/*

	Device Discovery Thread.

	This thread wakes every 'discovery_interval' seconds and trys to connect to
	those configured devices which are not connected. This saves the cell phone user
	from having to manually connect his/her cell phone to the asterisk box.
	Once a successful connection is made, a monitor thread is spun on the device which
	lives for the lifetime of the connection.

*/

static void *do_discovery(void *data)
{

	struct cel_pvt *pvt = data;

	for (;;) {
		AST_LIST_TRAVERSE(&devices, pvt, entry) {
			if (!pvt->connected) {
				if ((pvt->rfcomm_socket = rfcomm_connect(pvt->bdaddr, pvt->rfcomm_port)) > -1) {
					pvt->state = 0;
					if (start_monitor(pvt)) {
						pvt->connected = 1;
						if (option_verbose > 2)
							ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s has connected.\n", pvt->id);
					}
				}
			}
		}
		/* Go to sleep */
		sleep(discovery_interval);
	}

	return NULL;
}

/*

	This thread listens for incoming sco connections.
	Although the Bluetooth Handsfree Profile Specification says that either end may initiate the audio connection,
	in practice some devices (LG TU500) get upset unless they initiate the connection.
	We leave all sco initiation to the device.
	On an inbound sco connection, we need to find the appropriate device, and set the channel fd accordingly.

*/

static void *do_sco_listen(void *data)
{

	int ns;
	bdaddr_t local;
	struct sockaddr_sco addr;
	struct sco_options so;
	socklen_t len;
	int opt = 1;
	char saddr[18];
	socklen_t addrlen;
	struct cel_pvt *pvt;

	hci_devba(0, &local);

	if ((sco_socket = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO)) < 0) {
		ast_log(LOG_ERROR, "Unable to create sco listener socket.\n");
		return NULL;
	}
	memset(&addr, 0, sizeof(addr));
	addr.sco_family = AF_BLUETOOTH;
	bacpy(&addr.sco_bdaddr, &local);
	if (bind(sco_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		ast_log(LOG_ERROR, "Unable to bind sco listener socket. (%d)\n", errno);
		close(sco_socket);
		return NULL;
	}
	if (setsockopt(sco_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
		ast_log(LOG_ERROR, "Unable to setsockopt sco listener socket.\n");
		close(sco_socket);
		return NULL;
	}
	if (listen(sco_socket, 5) < 0) {
		ast_log(LOG_ERROR, "Unable to listen sco listener socket.\n");
		close(sco_socket);
		return NULL;
	}
	while (1) {
		addrlen = sizeof(struct sockaddr);
		if ((ns = accept(sco_socket, (struct sockaddr *)&addr, &addrlen)) > -1) {
			ba2str(&addr.sco_bdaddr, saddr);

			if (fcntl(ns, F_SETFL, O_RDWR|O_NONBLOCK) != 0) {
				ast_log(LOG_ERROR, "Failed to set NONBLOCK on sco socket.\n");
			}

			len = sizeof(so);
			getsockopt(ns, SOL_SCO, SCO_OPTIONS, &so, &len);

			if (option_debug)
				ast_log(LOG_DEBUG, "Incoming Audio Connection from device %s MTU is %d\n", saddr, so.mtu);

			pvt = NULL;
			AST_LIST_TRAVERSE(&devices, pvt, entry) {
				if (!strcmp(pvt->bdaddr, saddr))
					break;
			}
			if (pvt) {
				if (pvt->sco_socket != -1)
					close(pvt->sco_socket);
				pvt->sco_socket = ns;
				if (pvt->owner) {
#ifdef HAS_ASTERISK_1_2
					ast_mutex_lock(&pvt->owner->lock);
#else
					ast_channel_lock(pvt->owner);
#endif
					pvt->owner->fds[0] = ns;
#ifdef HAS_ASTERISK_1_2
					ast_mutex_unlock(&pvt->owner->lock);
#else
					ast_channel_unlock(pvt->owner);
#endif
				}
			} else if (option_debug)
				ast_log(LOG_DEBUG, "Could not find device for incoming Audio Connection.\n");
		}
	}

	return NULL;

}

static int cel_load_config(void)
{

	struct ast_config *cfg = NULL;
	char *cat = NULL;
	struct ast_variable *var;
	const char *address, *port, *context;
	struct cel_pvt *pvt;

	cfg = ast_config_load(CEL_CONFIG);
	if (!cfg)
		return 0;

	for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
		if (!strcasecmp(var->name, "interval"))
			discovery_interval = atoi(var->value);
	}

	cat = ast_category_browse(cfg, NULL);
	while (cat) {
		if (strcasecmp(cat, "general")) {
			if (option_debug)
				ast_log(LOG_DEBUG, "Loading device %s.\n", cat);
			address = ast_variable_retrieve(cfg, cat, "address");
			port = ast_variable_retrieve(cfg, cat, "port");
			context = ast_variable_retrieve(cfg, cat, "context");
			if (address && port) {
#ifdef HAS_ASTERISK_1_2
				if ((pvt = malloc(sizeof(struct cel_pvt)))) {
#else
				if ((pvt = ast_malloc(sizeof(struct cel_pvt)))) {
#endif
					ast_copy_string(pvt->id, cat, sizeof(pvt->id));
					ast_copy_string(pvt->bdaddr, address, sizeof(pvt->bdaddr));
					if (context)
						ast_copy_string(pvt->context, context, sizeof(pvt->context));
					else
						ast_copy_string(pvt->context, "default", sizeof(pvt->context));
					pvt->connected = 0;
					pvt->state = CEL_STATE_INIT;
					pvt->rfcomm_socket = -1;
					pvt->rfcomm_port = atoi(port);
					pvt->rfcomm_buf[0] = 0x00;
					pvt->sco_socket = -1;
					pvt->monitor_thread = AST_PTHREADT_NULL;
					pvt->owner = NULL;
					pvt->no_callsetup = 0;
					pvt->has_sms = 0;
					pvt->dsp = ast_dsp_new();
					pvt->dtmfrelax = 0;
					ast_dsp_digitmode(pvt->dsp, DSP_DIGITMODE_DTMF | pvt->dtmfrelax);
					pvt->dsp_features = DSP_FEATURE_DTMF_DETECT;
					ast_dsp_set_features(pvt->dsp, pvt->dsp_features);
					AST_LIST_INSERT_HEAD(&devices, pvt, entry);
				}
			} else {
				ast_log(LOG_ERROR, "Device %s has no address/port configured. It wont be enabled.\n", cat);
			}
		}
		cat = ast_category_browse(cfg, cat);
	}

	ast_config_destroy(cfg);

	return 1;

}

#ifndef HAS_ASTERISK_1_2
static int reload_module(void)
{

	return 0;

}
#endif

#ifdef HAS_ASTERISK_1_2
int unload_module(void)
#else
static int unload_module(void)
#endif
{

	struct cel_pvt *pvt;

	/* First, take us out of the channel loop */
	ast_channel_unregister(&cel_tech);

	/* Kill the discovery thread */
	if (discovery_thread != AST_PTHREADT_NULL) {
		pthread_cancel(discovery_thread);
		pthread_join(discovery_thread, NULL);
	}
	/* Kill the sco listener thread */
	if (sco_listener_thread != AST_PTHREADT_NULL) {
		pthread_cancel(sco_listener_thread);
		pthread_join(sco_listener_thread, NULL);
	}
	if ((close(sco_socket) == -1))
		ast_log(LOG_ERROR, "Unable to close sco_socket %d.\n", errno);

	/* Unregister the CLI & APP */
	ast_cli_unregister_multiple(cel_cli, sizeof(cel_cli) / sizeof(cel_cli[0]));
	ast_unregister_application(app_celstatus);
	ast_unregister_application(app_celsendsms);

	/* Destroy the device list */
	while ((pvt = AST_LIST_REMOVE_HEAD(&devices, entry))) {
		if (pvt->monitor_thread != AST_PTHREADT_NULL) {
			pthread_cancel(pvt->monitor_thread);
			pthread_join(pvt->monitor_thread, NULL);
		}
		if (pvt->sco_socket > -1) {
			close(pvt->sco_socket);
		}
		if (pvt->rfcomm_socket > -1) {
			close(pvt->rfcomm_socket);
		}
		ast_dsp_free(pvt->dsp);
		free(pvt);
	}
	
	return 0;

}

#ifdef HAS_ASTERISK_1_2
int load_module(void)
#else
static int load_module(void)
#endif
{

	int dev_id, s;

	/* Check if we have Bluetooth, no point loading otherwise... */
	dev_id = hci_get_route(NULL);
	s = hci_open_dev(dev_id);
	if (dev_id < 0 || s < 0) {
		ast_log(LOG_ERROR, "No Bluetooth device found. Not loading module.\n");
#ifdef HAS_ASTERISK_1_2
		return 0;
#else
		return AST_MODULE_LOAD_DECLINE;
#endif
	}
	hci_close_dev(s);

	if (!cel_load_config()) {
		ast_log(LOG_ERROR, "Unable to read config file %s. Not loading module.\n", CEL_CONFIG);
#ifdef HAS_ASTERISK_1_2
		return 0;
#else
		return AST_MODULE_LOAD_DECLINE;
#endif
	}

	/* Spin the discovery thread */
#ifdef HAS_ASTERISK_1_2
	if (ast_pthread_create(&discovery_thread, NULL, do_discovery, NULL) < 0) {
#else
	if (ast_pthread_create_background(&discovery_thread, NULL, do_discovery, NULL) < 0) {
#endif
		ast_log(LOG_ERROR, "Unable to create discovery thread.\n");
#ifdef HAS_ASTERISK_1_2
		return -1;
#else
		return AST_MODULE_LOAD_DECLINE;
#endif
	}
	/* Spin the sco listener thread */
#ifdef HAS_ASTERISK_1_2
	if (ast_pthread_create(&sco_listener_thread, NULL, do_sco_listen, NULL) < 0) {
#else
	if (ast_pthread_create_background(&sco_listener_thread, NULL, do_sco_listen, NULL) < 0) {
#endif
		ast_log(LOG_ERROR, "Unable to create sco listener thread.\n");
		pthread_cancel(discovery_thread);
		pthread_join(discovery_thread, NULL);
#ifdef HAS_ASTERISK_1_2
		return -1;
#else
		return AST_MODULE_LOAD_DECLINE;
#endif
	}

	ast_cli_register_multiple(cel_cli, sizeof(cel_cli) / sizeof(cel_cli[0]));
	ast_register_application(app_celstatus, cel_status_exec, celstatus_synopsis, celstatus_desc);
	ast_register_application(app_celsendsms, cel_sendsms_exec, celsendsms_synopsis, celsendsms_desc);

	/* Make sure we can register our channel type */
	if (ast_channel_register(&cel_tech)) {
		ast_log(LOG_ERROR, "Unable to register channel class %s\n", type);
		return -1;
	}
	return 0;
}

#ifdef HAS_ASTERISK_1_2
static char *ccdesc = "Bluetooth Cellphone Driver";
char *description()
{
	return ccdesc;
}

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

char *key()
{
	return ASTERISK_GPL_KEY;
}
							
#else
AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Bluetooth Cellphone Driver",
		.load = load_module,
		.unload = unload_module,
		.reload = reload_module,
);
#endif
