/*
 * 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 Mobile Device channel driver
 *
 * \author Dave Bowerman <david.bowerman@gmail.com>
 *
 * \ingroup channel_drivers
 */

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

#include <asterisk.h>

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

#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 <bluetooth/l2cap.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>
#include <asterisk/app.h>
#include <asterisk/manager.h>

#define AST_MODULE "chan_mobile"

#define MBL_CONFIG "mobile.conf"

#define DEVICE_FRAME_SIZE 48
#define DEVICE_FRAME_FORMAT AST_FORMAT_SLINEAR
#define CHANNEL_FRAME_SIZE 320

static int prefformat = DEVICE_FRAME_FORMAT;

static int discovery_interval = 60;			/* The device discovery interval, default 60 seconds. */
static pthread_t discovery_thread = AST_PTHREADT_NULL;	/* The discovery thread */
static sdp_session_t *sdp_session;

enum mbl_type {
	MBL_TYPE_PHONE,
	MBL_TYPE_HEADSET
};

enum mbl_state {
	MBL_STATE_INIT = 0,
	MBL_STATE_INIT1,
	MBL_STATE_INIT2,
	MBL_STATE_INIT3,
	MBL_STATE_INIT4,
	MBL_STATE_INIT5,
	MBL_STATE_INIT6,
	MBL_STATE_PREIDLE,
	MBL_STATE_IDLE,
	MBL_STATE_DIAL,
	MBL_STATE_DIAL1,
	MBL_STATE_OUTGOING,
	MBL_STATE_RING,
	MBL_STATE_RING2,
	MBL_STATE_RING3,
	MBL_STATE_INCOMING,
	MBL_STATE_HANGUP,
	MBL_STATE_INSMS,
	MBL_STATE_OUTSMS,
	MBL_STATE_OUTSMS1,
	MBL_STATE_OUTSMS2
};

struct adapter_pvt {
	int dev_id;					/* device id */
	int hci_socket;					/* device descriptor */
	char id[31];					/* the 'name' from mobile.conf */
	bdaddr_t addr;					/* adddress of adapter */
	unsigned int inuse:1;				/* are we in use ? */
	unsigned int alignment_detection:1;		/* do alignment detection on this adpater? */
	int sco_socket;
	AST_LIST_ENTRY(adapter_pvt) entry;
};

static AST_LIST_HEAD_STATIC(adapters, adapter_pvt);

struct mbl_pvt {
	struct ast_channel *owner;			/* Channel we belong to, possibly NULL */
	struct ast_frame fr;				/* "null" frame */
	enum mbl_type type;				/* Phone or Headset */
	char id[31];					/* The id from mobile.conf */
	int group;					/* group number for group dialling */
	bdaddr_t addr;					/* address of device */
	struct adapter_pvt *adapter;			/* the adapter we use */
	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];
	char io_buf[CHANNEL_FRAME_SIZE + AST_FRIENDLY_OFFSET];
	char io_save_buf[DEVICE_FRAME_SIZE];
	int io_save_len;
	int io_pipe[2];
	int sco_socket;					/* sco socket descriptor */
	pthread_t sco_listener_thread;			/* inbound sco listener for this device */
	enum mbl_state state;				/* monitor thread current state */
	pthread_t monitor_thread;			/* monitor thread handle */
	char dial_number[AST_MAX_EXTENSION];		/* number for the monitor thread to dial */
	int dial_timeout;
	char ciev_call_0[4];				/* dynamically built 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];
	unsigned int no_callsetup:1;
	unsigned int has_sms:1;
	unsigned int sent_answer:1;
	unsigned int do_alignment_detection:1;
	unsigned int alignment_detection_triggered:1;
	unsigned int do_hangup:1;
	short alignment_samples[4];
	int alignment_count;
	char sms_txt[160];
	struct ast_dsp *dsp;
	struct ast_frame *dsp_fr;
	int dtmf_skip;
	int skip_frames;
	char hangup_count;
	AST_LIST_ENTRY(mbl_pvt) entry;
};

static AST_LIST_HEAD_STATIC(devices, mbl_pvt);

/* CLI stuff */
static const char show_usage[] =
"Usage: mobile show devices\n" 
"       Shows the state of Bluetooth Cell / Mobile devices.\n";

static const char search_usage[] =
"Usage: mobile search\n" 
"       Searches for Bluetooth Cell / Mobile devices in range.\n";

static const char rfcomm_usage[] =
"Usage: mobile rfcomm command\n" 
"       Send command to the rfcomm port.\n";

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

static struct ast_cli_entry mbl_cli[] = {
	{{"mobile", "show", "devices", NULL}, do_show_devices, "Show Bluetooth Cell / Mobile devices", show_usage},
	{{"mobile", "search", NULL}, do_search_devices, "Search for Bluetooth Cell / Mobile devices", search_usage},
	{{"mobile", "rfcomm", NULL}, do_send_rfcomm, "Send commands to the rfcomm port for debugging", rfcomm_usage},
};

/* App stuff */
static char *app_mblstatus = "MobileStatus";
static char *mblstatus_synopsis = "MobileStatus(Device,Variable)";
static char *mblstatus_desc =
"MobileStatus(Device,Variable)\n"
"  Device - Id of mobile device from mobile.conf\n"
"  Variable - Variable to store status in will be 1-3.\n" 
"             In order, Disconnected, Connected & Free, Connected & Busy.\n";

static char *app_mblsendsms = "MobileSendSMS";
static char *mblsendsms_synopsis = "MobileSendSMS(Device,Dest,Message)";
static char *mblsendsms_desc =
"MobileSendSms(Device,Dest,Message)\n"
"  Device - Id of device from mobile.conf\n"
"  Dest - destination\n"
"  Message - text of the message\n";

static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num);
static struct ast_channel *mbl_request(const char *type, int format, void *data, int *cause);
static int mbl_call(struct ast_channel *ast, char *dest, int timeout);
static int mbl_hangup(struct ast_channel *ast);
static int mbl_answer(struct ast_channel *ast);
static int mbl_digit_begin(struct ast_channel *ast, char digit);
static int mbl_digit_end(struct ast_channel *ast, char digit, unsigned int duration);
static struct ast_frame *mbl_read(struct ast_channel *ast);
static int mbl_write(struct ast_channel *ast, struct ast_frame *frame);
static int mbl_fixup(struct ast_channel *oldchan, struct ast_channel *newchan);
static int mbl_devicestate(void *data);

static void do_alignment_detection(struct mbl_pvt *pvt, char *buf, int buflen);

static int rfcomm_connect(bdaddr_t src, bdaddr_t dst, int remote_channel);
static int rfcomm_write(struct mbl_pvt *pvt, char *buf);
static int rfcomm_read(struct mbl_pvt *pvt, char *buf, char flush, int timeout);

static int sco_connect(bdaddr_t src, bdaddr_t dst);
static int sco_write(int s, char *buf, int len);
static int sco_read(int s, char *buf, int len);

static void *do_sco_listen(void *data);
static int sdp_search(char *addr, int profile);

static const struct ast_channel_tech mbl_tech = {
	.type = "Mobile",
	.description = "Bluetooth Mobile Device Channel Driver",
	.capabilities = AST_FORMAT_SLINEAR,
	.requester = mbl_request,
	.call = mbl_call,
	.hangup = mbl_hangup,
	.answer = mbl_answer,
	.send_digit_begin = mbl_digit_begin,
	.send_digit_end = mbl_digit_end,
	.read = mbl_read,
	.write = mbl_write,
	.fixup = mbl_fixup,
	.devicestate = mbl_devicestate
};

/* CLI Commands implementation */


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

	struct mbl_pvt *pvt;
	char bdaddr[18];
	char group[6];

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

	ast_cli(fd, FORMAT, "ID", "Address", "Group", "Adapter", "Connected", "State", "SMS");
	AST_LIST_TRAVERSE(&devices, pvt, entry) {
		ba2str(&pvt->addr, bdaddr);
		snprintf(group, 5, "%d", pvt->group);
		ast_cli(fd, FORMAT, pvt->id, bdaddr, group, pvt->adapter->id, pvt->connected ? "Yes" : "No",
			(pvt->state == MBL_STATE_IDLE) ? "Free" : (pvt->state < MBL_STATE_IDLE) ? "Init" : "Busy",
			(pvt->has_sms) ? "Yes" : "No");
	}

	return RESULT_SUCCESS;

}

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

	struct adapter_pvt *adapter;
	inquiry_info *ii = NULL;
	int max_rsp, num_rsp;
	int len, flags;
	int i, phport, hsport;
	char addr[19] = {0};
	char name[31] = {0};

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

	/* find a free adapter */
	AST_LIST_TRAVERSE(&adapters, adapter, entry) {
		if (!adapter->inuse)
			break;
	}
	if (!adapter) {
		ast_cli(fd, "All Bluetooth adapters are in use at this time.\n");
		return RESULT_SUCCESS;
	}

	len  = 8;
	max_rsp = 255;
	flags = IREQ_CACHE_FLUSH;

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

#undef FORMAT2
#undef FORMAT3

	return RESULT_SUCCESS;
}

static int do_send_rfcomm(int fd, int argc, char **argv)
{
	char buf[128];
	struct mbl_pvt *pvt = NULL;

	if (argc != 4) {
		ast_cli(fd, "You must specify the name of the device and command.\n");
		return RESULT_SUCCESS;
	}

	AST_LIST_TRAVERSE(&devices, pvt, entry) {
		if (!strcmp(pvt->id, argv[2]))
			break;
	}

	if (!pvt || !pvt->connected) {
		ast_cli(fd, "Device %s not found.\n", argv[2]);
		return RESULT_SUCCESS;
	}

	snprintf(buf, sizeof(buf), "%s\r", argv[3]);
	rfcomm_write(pvt, buf);

	return RESULT_SUCCESS;
}

/*

	Dialplan applications implementation

*/

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

	struct mbl_pvt *pvt;
	char *parse;
	int stat;
	char status[2];

	AST_DECLARE_APP_ARGS(args,
		AST_APP_ARG(device);
		AST_APP_ARG(variable);
	);

	if (ast_strlen_zero(data))
		return -1;

	parse = ast_strdupa(data);

	AST_STANDARD_APP_ARGS(args, parse);

	if (ast_strlen_zero(args.device) || ast_strlen_zero(args.variable))
		return -1;

	stat = 1;

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

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

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

	return 0;

}

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

	struct mbl_pvt *pvt;
	char *parse;

	AST_DECLARE_APP_ARGS(args,
		AST_APP_ARG(device);
		AST_APP_ARG(dest);
		AST_APP_ARG(message);
	);

	if (ast_strlen_zero(data))
		return -1;

	parse = ast_strdupa(data);

	AST_STANDARD_APP_ARGS(args, parse);

	if (ast_strlen_zero(args.device)) {
		ast_log(LOG_ERROR,"NULL device for message -- SMS will not be sent.\n");
		return -1;
	}

	if (ast_strlen_zero(args.dest)) {
		ast_log(LOG_ERROR,"NULL destination for message -- SMS will not be sent.\n");
		return -1;
	}

	if (ast_strlen_zero(args.message)) {
		ast_log(LOG_ERROR,"NULL Message to be sent -- SMS will not be sent.\n");
		return -1;
	}

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

	if (!pvt) {
		ast_log(LOG_ERROR,"Bluetooth device %s wasn't found in the list -- SMS will not be sent.\n", args.device);
		return -1;
	}

	if (!pvt->connected) {
		ast_log(LOG_ERROR,"Bluetooth device %s wasn't connected -- SMS will not be sent.\n", args.device);
		return -1;
	}

	if (!pvt->has_sms) {
		ast_log(LOG_ERROR,"Bluetooth device %s doesn't handle SMS -- SMS will not be sent.\n", args.device);
		return -1;
	}

	if (pvt->state != MBL_STATE_IDLE) {
		ast_log(LOG_ERROR,"Bluetooth device %s isn't IDLE -- SMS will not be sent.\n", args.device);
		return -1;
	}

	ast_copy_string(pvt->dial_number, args.dest, sizeof(pvt->dial_number));
	ast_copy_string(pvt->sms_txt, args.message, sizeof(pvt->sms_txt));
	pvt->state = MBL_STATE_OUTSMS;

	return 0;

}

/*

	Channel Driver callbacks

*/

static struct ast_channel *mbl_new(int state, struct mbl_pvt *pvt, char *cid_num)
{

	struct ast_channel *chn;

	if (pipe(pvt->io_pipe) == -1) {
		ast_log(LOG_ERROR, "Failed to create io_pipe.\n");
		return NULL;
	}

	if (pvt->sco_socket != -1)
		close(pvt->sco_socket);
	pvt->sco_socket = -1;
	pvt->io_save_len = 0;
	pvt->sent_answer = 0;
	pvt->skip_frames = 0;
	pvt->alignment_count = 0;
	pvt->alignment_detection_triggered = 0;
	if (pvt->adapter->alignment_detection)
		pvt->do_alignment_detection = 1;
	else
		pvt->do_alignment_detection = 0;
	pvt->do_hangup = 1;
	chn = ast_channel_alloc(1, state, cid_num, pvt->id, 0, 0, pvt->context, 0, "Mobile/%s-%04lx", pvt->id, ast_random() & 0xffff);
	if (chn) {
		chn->tech = &mbl_tech;
		chn->nativeformats = prefformat;
		chn->rawreadformat = prefformat;
		chn->rawwriteformat = prefformat;
		chn->writeformat = prefformat;
		chn->readformat = prefformat;
		chn->tech_pvt = pvt;
		chn->fds[0] = pvt->io_pipe[0];
		if (state == AST_STATE_RING)
			chn->rings = 1;
		ast_string_field_set(chn, language, "en");
		pvt->owner = chn;
		return chn;
	}

	return NULL;

}

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

	struct ast_channel *chn = NULL;
	struct mbl_pvt *pvt;
	char *dest_dev = NULL;
	char *dest_num = NULL;
	int oldformat, group;

	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)
		*dest_num++ = 0x00;

	/* Find requested device and make sure its connected. */
	AST_LIST_TRAVERSE(&devices, pvt, entry) {
		if (((dest_dev[0] == 'g') || (dest_dev[0] == 'G')) && ((dest_dev[1] >= '0') && (dest_dev[1] <= '9'))) {
			group = atoi(dest_dev+1);
			if (pvt->group == group)
				break;
		} else 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;
	}

	if ((pvt->type == MBL_TYPE_PHONE) && !dest_num) {
		ast_log(LOG_WARNING, "Cant determine destination number.\n");
		*cause = AST_CAUSE_INCOMPATIBLE_DESTINATION;
		return NULL;
	}

	chn = mbl_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 mbl_call(struct ast_channel *ast, char *dest, int timeout)
{

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

	dest_dev = ast_strdupa((char *)dest);

	pvt = ast->tech_pvt;

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

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

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

	if (pvt->type == MBL_TYPE_PHONE) {
		ast_copy_string(pvt->dial_number, dest_num, sizeof(pvt->dial_number));
		pvt->state = MBL_STATE_DIAL;
		pvt->dial_timeout = (timeout == 0) ? 30 : timeout;
	} else {
		pvt->state = MBL_STATE_RING;
	}

	return 0;

}

static int mbl_hangup(struct ast_channel *ast)
{

	struct mbl_pvt *pvt;

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

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

	ast->fds[0] = -1;
	
	close(pvt->io_pipe[0]);
	close(pvt->io_pipe[1]);

	if (pvt->type == MBL_TYPE_HEADSET && pvt->sco_socket != -1) {
		close(pvt->sco_socket);
		pvt->sco_socket = -1;
	}

	if ((pvt->state == MBL_STATE_INCOMING || pvt->state == MBL_STATE_OUTGOING || pvt->state == MBL_STATE_DIAL1 || pvt->state == MBL_STATE_RING3) && pvt->type == MBL_TYPE_PHONE) {
		if (pvt->do_hangup) {
			rfcomm_write(pvt, "AT+CHUP\r");
		}
		pvt->state = MBL_STATE_HANGUP;
		pvt->hangup_count = 0;
	} else
		pvt->state = MBL_STATE_IDLE;

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

	return 0;

}

static int mbl_answer(struct ast_channel *ast)
{

	struct mbl_pvt *pvt;

	pvt = ast->tech_pvt;

	rfcomm_write(pvt, "ATA\r");

	ast_setstate(ast, AST_STATE_UP);

	pvt->sent_answer = 1;

	return 0;

}

static int mbl_digit_begin(struct ast_channel *chan, char digit)
{

	return 0;

}

static int mbl_digit_end(struct ast_channel *ast, char digit, unsigned int duration)
{

	struct mbl_pvt *pvt;
	char buf[11];

	pvt = ast->tech_pvt;

	if (pvt->type == MBL_TYPE_HEADSET)
		return 0;

	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;

}

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

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

	//ast_debug(2, "*** mbl_read()\n");

	if (!pvt->owner) {
		return &ast_null_frame;
	}
	memset(&pvt->fr, 0x00, sizeof(struct ast_frame));
	pvt->fr.frametype = AST_FRAME_VOICE;
	pvt->fr.subclass = DEVICE_FRAME_FORMAT;
	pvt->fr.datalen = CHANNEL_FRAME_SIZE;
	pvt->fr.samples = CHANNEL_FRAME_SIZE / 2;
	pvt->fr.src = "Mobile";
	pvt->fr.offset = AST_FRIENDLY_OFFSET;
	pvt->fr.mallocd = 0;
	pvt->fr.delivery.tv_sec = 0;
	pvt->fr.delivery.tv_usec = 0;
	pvt->fr.data = pvt->io_buf + AST_FRIENDLY_OFFSET;

	if ((r = read(pvt->io_pipe[0], pvt->fr.data, CHANNEL_FRAME_SIZE)) != CHANNEL_FRAME_SIZE) {
		if (r == -1) {
			ast_log(LOG_ERROR, "read error %d\n", errno);
			return &ast_null_frame;
		} else {
			pvt->fr.datalen = r;
			pvt->fr.samples = r / 2;
		}
	}

	f = ast_dsp_process(0, pvt->dsp, &pvt->fr);
	if (f && (f->frametype == AST_FRAME_DTMF_END)) {
		pvt->fr.frametype = AST_FRAME_DTMF_END;
		pvt->fr.subclass = f->subclass;
	}

	return &pvt->fr;

}

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

	struct mbl_pvt *pvt = ast->tech_pvt;
	int i, r, io_need, num_frames;
	char *pfr, buf[DEVICE_FRAME_SIZE];

	//ast_debug(2, "*** mbl_write\n");

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

	io_need = 0;
	if (pvt->io_save_len > 0) {
		io_need = DEVICE_FRAME_SIZE - pvt->io_save_len;
		memcpy(pvt->io_save_buf + pvt->io_save_len, frame->data, io_need);
		sco_write(pvt->sco_socket, pvt->io_save_buf, DEVICE_FRAME_SIZE);
		if ((r = sco_read(pvt->sco_socket, buf, DEVICE_FRAME_SIZE))) {
			if (pvt->do_alignment_detection)
				do_alignment_detection(pvt, buf, r);
			sco_write(pvt->io_pipe[1], buf, r);
		}
	}

	num_frames = (frame->datalen - io_need) / DEVICE_FRAME_SIZE;
	pfr = frame->data + io_need;

	for (i=0; i<num_frames; i++) {
		sco_write(pvt->sco_socket, pfr, DEVICE_FRAME_SIZE);
		if ((r = sco_read(pvt->sco_socket, buf, DEVICE_FRAME_SIZE))) {
			if (pvt->do_alignment_detection)
				do_alignment_detection(pvt, buf, r);
			sco_write(pvt->io_pipe[1], buf, r);
		}
		pfr += DEVICE_FRAME_SIZE;
	}

	pvt->io_save_len = (frame->datalen - io_need) - (num_frames * DEVICE_FRAME_SIZE);
	if (pvt->io_save_len > 0) {
		memcpy(pvt->io_save_buf, pfr, pvt->io_save_len);
	}

	return 0;

}

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

	struct mbl_pvt *pvt = oldchan->tech_pvt;

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

	return 0;

}

static int mbl_devicestate(void *data)
{

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

	device = ast_strdupa(S_OR(data, ""));

	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;

}

/*

	Callback helpers

*/

/*

	do_alignment_detection()

	This routine attempts to detect where we get misaligned sco audio data from the bluetooth adaptor.

	Its enabled by alignmentdetect=yes under the adapter entry in mobile.conf

	Some adapters suffer a problem where occasionally they will byte shift the audio stream one byte to the right.
	The result is static or white noise on the inbound (from the adapter) leg of the call.
	This is characterised by a sudden jump in magnitude of the value of the 16 bit samples.

	Here we look at the first 4 48 byte frames. We average the absolute values of each sample in the frame,
	then average the sum of the averages of frames 1, 2, and 3.
	Frame zero is usually zero.
	If the end result > 100, and it usually is if we have the problem, set a flag and compensate by shifting the bytes
	for each subsequent frame during the call.

	If the result is <= 100 then clear the flag so we dont come back in here...

	This seems to work OK....

*/

static void do_alignment_detection(struct mbl_pvt *pvt, char *buf, int buflen)
{

	int i;
	short a, *s;
	char *p;

	if (pvt->alignment_detection_triggered) {
		for (i=buflen, p=buf+buflen-1; i>0; i--, p--)
			*p = *(p-1);
		*(p+1) = 0;
		return;
	}

	if (pvt->alignment_count < 4) {
		s = (short *)buf;
		for (i=0, a=0; i<buflen/2; i++) {
			a += *s++;
			a /= i+1;
		}
		pvt->alignment_samples[pvt->alignment_count++] = a;
		return;
	}

	ast_log(LOG_DEBUG, "Alignment Detection result is [%-d %-d %-d %-d]\n", pvt->alignment_samples[0], pvt->alignment_samples[1], pvt->alignment_samples[2], pvt->alignment_samples[3]);

	a = abs(pvt->alignment_samples[1]) + abs(pvt->alignment_samples[2]) + abs(pvt->alignment_samples[3]);
	a /= 3;
	if (a > 100) {
		pvt->alignment_detection_triggered = 1;
		ast_log(LOG_DEBUG, "Alignment Detection Triggered.\n");
	} else
		pvt->do_alignment_detection = 0;

}

/*

	rfcomm helpers

*/

static int rfcomm_connect(bdaddr_t src, bdaddr_t dst, int remote_channel) {

	struct sockaddr_rc addr;
	int s;

	if ((s = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM)) < 0) {
		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, &src);
	addr.rc_channel = (uint8_t) 1;
	if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		ast_log(LOG_DEBUG, "bind() failed (%d).\n", errno);
		close(s);
		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) {
		ast_log(LOG_DEBUG, "connect() failed (%d).\n", errno);
		close(s);
		return -1;
	}

	return s;

}

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

	char *p;
	ssize_t num_write;
	int len;

	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 mbl_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, '\r'))) {
			*p++ = 0x00;
			if (*p == '\n')
				p++;
			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, '\r'))) {
					*p++ = 0x00;
					if (*p == '\n')
						p++;
					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;

}

/*

	sco helpers

*/

static int sco_connect(bdaddr_t src, bdaddr_t dst)
{

	struct sockaddr_sco addr;
	int s;

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

	memset(&addr, 0, sizeof(addr));
	addr.sco_family = AF_BLUETOOTH;
	bacpy(&addr.sco_bdaddr, &src);
	if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		ast_log(LOG_DEBUG, "bind() failed (%d).\n", errno);
		close(s);
		return -1;
	}

	memset(&addr, 0, sizeof(addr));
	addr.sco_family = AF_BLUETOOTH;
	bacpy(&addr.sco_bdaddr, &dst);

	if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		ast_log(LOG_DEBUG, "sco connect() failed (%d).\n", errno);
		close(s);
		return -1;
	}

	return s;

}

static int sco_write(int s, char *buf, int len)
{

	int r;

	if (s == -1) {
		ast_debug(2, "sco_write() not ready\n");
		return 0;
	}

	ast_debug(2, "sco_write()\n");

	r = write(s, buf, len);
	if (r == -1) {
		ast_debug(2, "sco write error %d\n", errno);
		return 0;
	}

	return 1;

}

static int sco_read(int s, char *buf, int len)
{

	int r;

	if (s == -1) {
		ast_debug(2, "sco_read() not ready\n");
		return 0;
	}

	ast_debug(2, "sco_read()\n");

	r = read(s, buf, len);
	if (r == -1) {
		ast_debug(2, "sco_read() error %d\n", errno);
		return 0;
	}

	return r;

}

/*

	sdp helpers

*/

static int sdp_search(char *addr, int profile)
{

	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) {
		ast_log(LOG_DEBUG, "sdp_connect() failed on device %s.\n", addr);
		return 0;
	}

	sdp_uuid32_create(&svc_uuid, profile);
	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
			ast_log(LOG_DEBUG, "No responses returned for device %s.\n", addr);
	} else
		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;

}

static sdp_session_t *sdp_register(void)
{

	uint32_t service_uuid_int[] = {0, 0, 0, GENERIC_AUDIO_SVCLASS_ID};
	uint8_t rfcomm_channel = 1;
	const char *service_name = "Asterisk PABX";
	const char *service_dsc = "Asterisk PABX";
	const char *service_prov = "Asterisk";

	uuid_t root_uuid, l2cap_uuid, rfcomm_uuid, svc_uuid, svc_class1_uuid, svc_class2_uuid;
	sdp_list_t  *l2cap_list = 0, *rfcomm_list = 0, *root_list = 0, *proto_list = 0, *access_proto_list = 0, *svc_uuid_list = 0;
	sdp_data_t *channel = 0;

	int err = 0;
	sdp_session_t *session = 0;

	sdp_record_t *record = sdp_record_alloc();

	sdp_uuid128_create(&svc_uuid, &service_uuid_int);
	sdp_set_service_id(record, svc_uuid);

	sdp_uuid32_create(&svc_class1_uuid, GENERIC_AUDIO_SVCLASS_ID);
	sdp_uuid32_create(&svc_class2_uuid, HEADSET_PROFILE_ID);

	svc_uuid_list = sdp_list_append(0, &svc_class1_uuid);
	svc_uuid_list = sdp_list_append(svc_uuid_list, &svc_class2_uuid);
	sdp_set_service_classes(record, svc_uuid_list);

	sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
	root_list = sdp_list_append(0, &root_uuid);
	sdp_set_browse_groups( record, root_list );

	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
	l2cap_list = sdp_list_append(0, &l2cap_uuid);
	proto_list = sdp_list_append(0, l2cap_list);

	sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID);
	channel = sdp_data_alloc(SDP_UINT8, &rfcomm_channel);
	rfcomm_list = sdp_list_append(0, &rfcomm_uuid);
	sdp_list_append(rfcomm_list, channel);
	sdp_list_append(proto_list, rfcomm_list);

	access_proto_list = sdp_list_append(0, proto_list);
	sdp_set_access_protos(record, access_proto_list);

	sdp_set_info_attr(record, service_name, service_prov, service_dsc);

	if (!(session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, SDP_RETRY_IF_BUSY)))
		ast_log(LOG_WARNING, "Failed to connect sdp and create session.\n");
	else
		err = sdp_record_register(session, record, 0);

	sdp_data_free(channel);
	sdp_list_free(rfcomm_list, 0);
	sdp_list_free(root_list, 0);
	sdp_list_free(access_proto_list, 0);
	sdp_list_free(svc_uuid_list, 0);

	return session;

}

/*

	Thread routines

*/

static void *do_monitor_phone(void *data)
{

	struct mbl_pvt *pvt = (struct mbl_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[160];

	brsf = nsmode = 0;

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

	while (monitor) {

		if (pvt->state == MBL_STATE_DIAL1)
			t = pvt->dial_timeout;
		else if (pvt->state == MBL_STATE_HANGUP)
			t = 2;
		else if (pvt->state == MBL_STATE_OUTSMS1)
			t = 2;
		else if (pvt->state == MBL_STATE_OUTSMS2)
			t = 10;
		else
			t = 1;

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

		if ((s > 0) && (buf[0] != 0x0) && (buf[0] != '\r')) {
			ast_log(LOG_DEBUG, "rfcomm_read() (%s) [%s]\n", pvt->id, buf);
			switch (pvt->state) {
			case MBL_STATE_INIT:
				if (strstr(buf, "+BRSF:")) {
					brsf = 1;
				} else if (strstr(buf, "ERROR") && !nsmode) {	/* Hmmm, Non-Standard Phone, just continue */
					rfcomm_write(pvt, "AT+CIND=?\r");
					pvt->state++;
					nsmode = 1;
				} else if (strstr(buf, "OK") && brsf) {
					rfcomm_write(pvt, "AT+CIND=?\r");
					pvt->state++;
				}
				break;
			case MBL_STATE_INIT1:
				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;
					ast_log(LOG_DEBUG, "CIEV_CALL=%d CIEV_CALLSETUP=%d\n", callp, callsetupp);
				}
				if (strstr(buf, "OK")) {
					rfcomm_write(pvt, "AT+CIND?\r");
					pvt->state++;
				}
				break;
			case MBL_STATE_INIT2:
				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")) {
					rfcomm_write(pvt, "AT+CMER=3,0,0,1\r");
					pvt->state++;
				}
				break;
			case MBL_STATE_INIT3:
				if (strstr(buf, "OK")) {
					rfcomm_write(pvt, "AT+CLIP=1\r");
					pvt->state++;
				}
				break;
			case MBL_STATE_INIT4:
				if (strstr(buf, "OK")) {
					rfcomm_write(pvt, "AT+CMGF=1\r");
					pvt->state++;
				}
				break;
			case MBL_STATE_INIT5:
				if (strstr(buf, "ERROR")) {	/* No SMS Support ! */
					pvt->state = MBL_STATE_PREIDLE;
				} else if (strstr(buf, "OK")) {
					rfcomm_write(pvt, "AT+CNMI=2,1,0,1,0\r");
					pvt->state++;
				}
				break;
			case MBL_STATE_INIT6:
				if (strstr(buf, "OK")) {	/* We have SMS Support */
					pvt->has_sms = 1;
					pvt->state = MBL_STATE_PREIDLE;
				} else if (strstr(buf, "ERROR")) {
					pvt->has_sms = 0;
					pvt->state = MBL_STATE_PREIDLE;
				}
				break;
			case MBL_STATE_PREIDLE: /* Nothing handled here, wait for timeout, then off we go... */
				break;
			case MBL_STATE_IDLE:
				ast_log(LOG_DEBUG, "Device %s [%s]\n", pvt->id, buf);
				if (strstr(buf, "RING")) {
					pvt->state = MBL_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 MBL_STATE_DIAL: /* Nothing handled here, we need to wait for a timeout */
				break;
			case MBL_STATE_DIAL1:
				if (strstr(buf, "OK")) {
					if (pvt->no_callsetup) {
						ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
					} else {
						ast_setstate(pvt->owner, AST_STATE_RINGING);
					}
					pvt->state = MBL_STATE_OUTGOING;
				}
				break;
			case MBL_STATE_OUTGOING:
				if (strstr(buf, "+CIEV")) {
					if (strstr(buf, pvt->ciev_call_0)) { 				/* call was hung up */
						pvt->do_hangup = 0;
						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 MBL_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;
						}
					}
					chn = mbl_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 = MBL_STATE_RING3;
					} else {
						ast_log(LOG_ERROR, "Unable to allocate channel for incoming call.\n");
						rfcomm_write(pvt, "AT+CHUP\r");
						pvt->state = MBL_STATE_IDLE;
					}
				}
				break;
			case MBL_STATE_RING2:
				chn = mbl_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 = MBL_STATE_RING3;
				} else {
					ast_log(LOG_ERROR, "Unable to allocate channel for incoming call.\n");
					rfcomm_write(pvt, "AT+CHUP\r");
					pvt->state = MBL_STATE_IDLE;
				}
				break;
			case MBL_STATE_RING3:
				if (strstr(buf, "+CIEV")) {
					if (strstr(buf, pvt->ciev_call_1)) {
						if (pvt->sent_answer) {	/* We answered */
							pvt->state = MBL_STATE_INCOMING;
						} else {		/* User answered on handset!, disconnect */
							pvt->state = MBL_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 MBL_STATE_INCOMING:
				if (strstr(buf, "+CIEV")) {
					if (strstr(buf, pvt->ciev_call_0)) {
						pvt->do_hangup = 0;
						ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
					}
				}
				break;
			case MBL_STATE_HANGUP:
				if (strstr(buf, "OK") || strstr(buf, pvt->ciev_call_0)) {
					close(pvt->sco_socket);
					pvt->sco_socket = -1;
					pvt->state = MBL_STATE_IDLE;
				}
				break;
			case MBL_STATE_INSMS:
				if (strstr(buf, "+CMGR:")) {
					memset(sms_src, 0x00, sizeof(sms_src));
					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")) {
					chn = mbl_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 = MBL_STATE_IDLE;
				} else {
					ast_copy_string(sms_txt, buf, sizeof(sms_txt));
				}
				break;
			case MBL_STATE_OUTSMS:
				break;
			case MBL_STATE_OUTSMS1:
				break;
			case MBL_STATE_OUTSMS2:
				if (strstr(buf, "OK")) {
					pvt->state = MBL_STATE_IDLE;
				}
				break;
			}
			/* Unsolicited responses */

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

		} else if (s == 0) { /* Timeouts */
			if (pvt->state == MBL_STATE_INIT2) { /* 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 == MBL_STATE_INIT3) { /* 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 == MBL_STATE_PREIDLE) {
				pvt->connected = 1;
				ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s initialised and ready.\n", pvt->id);
				pvt->state = MBL_STATE_IDLE;
			} else if (pvt->state == MBL_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 = MBL_STATE_IDLE;
				} else {
					pvt->state = MBL_STATE_DIAL1;
				}
			} else if (pvt->state == MBL_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 = MBL_STATE_IDLE;
			} else if (pvt->state == MBL_STATE_RING) { /* No CLIP?, bump it */
				pvt->state = MBL_STATE_RING2;
			} else if (pvt->state == MBL_STATE_HANGUP) {
				if (pvt->do_hangup) {
					if (pvt->hangup_count == 6) {
						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
					pvt->state = MBL_STATE_IDLE;
			} else if (pvt->state == MBL_STATE_OUTSMS) {
				sprintf(buf, "AT+CMGS=\"%s\"\r", pvt->dial_number);
				rfcomm_write(pvt, buf);
				pvt->state = MBL_STATE_OUTSMS1;
			} else if (pvt->state == MBL_STATE_OUTSMS1) {
				if (pvt->rfcomm_buf[0] == '>') {
					snprintf(buf, sizeof(buf), "%s%c", pvt->sms_txt, 0x1a);
					rfcomm_write(pvt, buf);
					pvt->state = MBL_STATE_OUTSMS2;
				} else {
					ast_log(LOG_ERROR, "Failed to send SMS to %s on device %s\n", pvt->dial_number, pvt->id);
					pvt->state = MBL_STATE_IDLE;
				}
			} else if (pvt->state == MBL_STATE_OUTSMS2) {
				ast_log(LOG_ERROR, "Failed to send SMS to %s on device %s\n", pvt->dial_number, pvt->id);
				pvt->state = MBL_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;
		}

	}

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

	pthread_cancel(pvt->sco_listener_thread);
	pthread_join(pvt->sco_listener_thread, NULL);
	pvt->sco_listener_thread = AST_PTHREADT_NULL;

	close(pvt->adapter->sco_socket);

	manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Disconnect\r\nDevice: %s\r\n", pvt->id);

	pvt->adapter->inuse = 0;

	return NULL;

}

static void *do_monitor_headset(void *data)
{

	struct mbl_pvt *pvt = (struct mbl_pvt *)data;
	char monitor = 1;
	char buf[256];
	int s, t;

	pvt->state = MBL_STATE_PREIDLE;

	while (monitor) {

		if (pvt->state == MBL_STATE_RING2)
			t = 2;
		else
			t = 1;
		s = rfcomm_read(pvt, buf, 0, t);

		if ((s > 0) && (buf[0] != 0x0) && (buf[0] != '\r')) {
			ast_log(LOG_DEBUG, "rfcomm_read() (%s) [%s]\n", pvt->id, buf);
			switch (pvt->state) {
			case MBL_STATE_RING2:
				if (strstr(buf, "AT+CKPD=")) {
					ast_queue_control(pvt->owner, AST_CONTROL_ANSWER);
					pvt->state = MBL_STATE_INCOMING;
					rfcomm_write(pvt, "\r\n+VGS=13\r\n");
					rfcomm_write(pvt, "\r\n+VGM=13\r\n");
				}
				break;
			case MBL_STATE_INCOMING:
				if (strstr(buf, "AT+CKPD=")) {
					ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
				}
				break;
			default:
				break;
			}
			if (strstr(buf, "AT+VGS=")) {
				rfcomm_write(pvt, "\r\nOK\r\n");
			} else if (strstr(buf, "AT+VGM=")) {
				rfcomm_write(pvt, "\r\nOK\r\n");
			}
		} else if (s == 0) {	/* Timeouts */
			if (pvt->state == MBL_STATE_PREIDLE) {
				pvt->connected = 1;
				ast_verbose(VERBOSE_PREFIX_3 "Bluetooth Device %s initialised and ready.\n", pvt->id);
				pvt->state = MBL_STATE_IDLE;
			} else if (pvt->state == MBL_STATE_RING) {
				pvt->sco_socket = sco_connect(pvt->adapter->addr, pvt->addr);
				if (pvt->sco_socket > -1) {
					ast_setstate(pvt->owner, AST_STATE_RINGING);
					ast_queue_control(pvt->owner, AST_CONTROL_RINGING);
					pvt->state = MBL_STATE_RING2;
				} else {
					ast_queue_control(pvt->owner, AST_CONTROL_HANGUP);
				}
			} else if (pvt->state == MBL_STATE_RING2) {
				rfcomm_write(pvt, "\r\nRING\r\n");
			}
		} 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;
		}

	}

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

	manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Disconnect\r\nDevice: %s\r\n", pvt->id);

	pvt->adapter->inuse = 0;

	return NULL;

}

static int start_monitor(struct mbl_pvt *pvt)
{

	if (pvt->type == MBL_TYPE_PHONE) {
		if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor_phone, pvt) < 0) {
			pvt->monitor_thread = AST_PTHREADT_NULL;
			return 0;
		}
		/* we are a phone, so spin the sco listener on the adapter as well */
		if (ast_pthread_create_background(&pvt->sco_listener_thread, NULL, do_sco_listen, pvt->adapter) < 0) {
			ast_log(LOG_ERROR, "Unable to create sco listener thread for device %s.\n", pvt->id);
		}

	} else {
		if (ast_pthread_create_background(&pvt->monitor_thread, NULL, do_monitor_headset, pvt) < 0) {
			pvt->monitor_thread = AST_PTHREADT_NULL;
			return 0;
		}
	}

	return 1;

}

static void *do_discovery(void *data)
{

	struct adapter_pvt *adapter;
	struct mbl_pvt *pvt;

	for (;;) {
		AST_LIST_TRAVERSE(&adapters, adapter, entry) {
			if (!adapter->inuse) {
				AST_LIST_TRAVERSE(&devices, pvt, entry) {
					if (!adapter->inuse && !pvt->connected && !strcmp(adapter->id, pvt->adapter->id)) {
						if ((pvt->rfcomm_socket = rfcomm_connect(adapter->addr, pvt->addr, pvt->rfcomm_port)) > -1) {
							pvt->state = 0;
							if (start_monitor(pvt)) {
								pvt->connected = 1;
								adapter->inuse = 1;
								manager_event(EVENT_FLAG_SYSTEM, "MobileStatus", "Status: Connect\r\nDevice: %s\r\n", pvt->id);
								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;
}

static void *do_sco_listen(void *data)
{

	int ns;
	struct sockaddr_sco addr;
	char saddr[18];
	struct sco_options so;
	socklen_t len;
	int opt = 1;
	socklen_t addrlen;
	struct mbl_pvt *pvt;
	struct adapter_pvt *adapter = (struct adapter_pvt *) data;

	if ((adapter->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, &adapter->addr);
	if (bind(adapter->sco_socket, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
		ast_log(LOG_ERROR, "Unable to bind sco listener socket. (%d)\n", errno);
		close(adapter->sco_socket);
		return NULL;
	}
	if (setsockopt(adapter->sco_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) == -1) {
		ast_log(LOG_ERROR, "Unable to setsockopt sco listener socket.\n");
		close(adapter->sco_socket);
		return NULL;
	}
	if (listen(adapter->sco_socket, 5) < 0) {
		ast_log(LOG_ERROR, "Unable to listen sco listener socket.\n");
		close(adapter->sco_socket);
		return NULL;
	}
	while (1) {
		ast_log(LOG_DEBUG, "About to accept() socket.\n");
		addrlen = sizeof(struct sockaddr_sco);
		if ((ns = accept(adapter->sco_socket, (struct sockaddr *)&addr, &addrlen)) > -1) {
			ast_log(LOG_DEBUG, "accept()ed socket.\n");
			len = sizeof(so);
			getsockopt(ns, SOL_SCO, SCO_OPTIONS, &so, &len);

			ba2str(&addr.sco_bdaddr, saddr);
			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 (!bacmp(&pvt->addr, &addr.sco_bdaddr)) 
					break;
			}
			if (pvt) {
				if (pvt->sco_socket != -1)
					close(pvt->sco_socket);
				pvt->sco_socket = ns;
			} else
				ast_log(LOG_DEBUG, "Could not find device for incoming Audio Connection.\n");
		} else {
			 ast_log(LOG_ERROR, "accept() failed %d\n", errno);
		}
	}

	return NULL;

}

/*

	Module

*/

static int mbl_load_config(void)
{

	struct ast_config *cfg = NULL;
	char *cat = NULL;
	struct ast_variable *var;
	const char *id, *address, *useadapter, *port, *context, *type, *skip, *group, *master, *nocallsetup, *aligndetect;
	struct mbl_pvt *pvt;
	struct adapter_pvt *adapter;
	uint16_t vs;
	struct hci_dev_req dr;
	char nadapters = 0;
	// struct ast_flags config_flags = { 0 };

	cfg = ast_config_load(MBL_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);
	}

	/* load adapters first */
	cat = ast_category_browse(cfg, NULL);
	while (cat) {
		if (!strcasecmp(cat, "adapter")) {
			id = ast_variable_retrieve(cfg, cat, "id");
			address = ast_variable_retrieve(cfg, cat, "address");
			master = ast_variable_retrieve(cfg, cat, "forcemaster");
			aligndetect = ast_variable_retrieve(cfg, cat, "alignmentdetection");
			ast_log(LOG_DEBUG, "Loading adapter %s %s.\n", id, address);
			if (!ast_strlen_zero(id) && !ast_strlen_zero(address)) {
				if ((adapter = ast_calloc(1, sizeof(*adapter)))) {
					ast_copy_string(adapter->id, id, sizeof(adapter->id));
					str2ba(address, &adapter->addr);
					if (!ast_strlen_zero(aligndetect)) {
						if (*aligndetect == 'Y' || *aligndetect == 'y')
							adapter->alignment_detection = 1;
					}
					adapter->dev_id = hci_devid(address);
					adapter->hci_socket = hci_open_dev(adapter->dev_id);
					if (adapter->dev_id < 0 || adapter->hci_socket < 0) {
						ast_log(LOG_ERROR, "Unable to open adapter %s. It won't be enabled.\n", adapter->id);
						ast_free(adapter);
					} else {
						if ((master) && (*master)) {
							dr.dev_id = adapter->dev_id;
							if (hci_strtolm("master", &dr.dev_opt)) {
								if (ioctl(adapter->hci_socket, HCISETLINKMODE, (unsigned long) &dr) < 0) {
									ast_log(LOG_WARNING, "Unable to set adapter %s link mode to MASTER.\n", adapter->id);
								}
							}
						}
						hci_read_voice_setting(adapter->hci_socket, &vs, 1000);
						vs = htobs(vs);
						if (vs != 0x0060) {
							ast_log(LOG_ERROR, "Incorrect voice setting for adapter %s, it must be 0x0060 - see 'man hciconfig' for details.\n", adapter->id);
							hci_close_dev(adapter->hci_socket);
							ast_free(adapter);
						} else {
							AST_LIST_INSERT_HEAD(&adapters, adapter, entry);
							nadapters++;
						}
					}
				}
			} else
				ast_log(LOG_ERROR, "id/address missing for adapter %s. It won't be enabled.\n", cat);
		}
		cat = ast_category_browse(cfg, cat);
	}

	if (!nadapters) {
		ast_log(LOG_WARNING, "***********************************************************************\n");
		ast_log(LOG_WARNING, "No Adapters defined. Please review mobile.conf. See sample for details.\n");
		ast_log(LOG_WARNING, "***********************************************************************\n");
	}

	/* now load devices */
	cat = ast_category_browse(cfg, NULL);
	while (cat) {
		if (strcasecmp(cat, "general") && strcasecmp(cat, "adapter")) {
			ast_log(LOG_DEBUG, "Loading device %s.\n", cat);
			address = ast_variable_retrieve(cfg, cat, "address");
			useadapter = ast_variable_retrieve(cfg, cat, "adapter");
			port = ast_variable_retrieve(cfg, cat, "port");
			context = ast_variable_retrieve(cfg, cat, "context");
			type = ast_variable_retrieve(cfg, cat, "type");
			skip = ast_variable_retrieve(cfg, cat, "dtmfskip");
			group = ast_variable_retrieve(cfg, cat, "group");
			nocallsetup = ast_variable_retrieve(cfg, cat, "nocallsetup");
			if (!ast_strlen_zero(address) && !ast_strlen_zero(port) && !ast_strlen_zero(useadapter)) {
				/* find the adapter */
				AST_LIST_TRAVERSE(&adapters, adapter, entry) {
					if (!strcmp(adapter->id, useadapter))
						break;
				}
				if (!adapter) {
					ast_log(LOG_ERROR, "Device %s configured to use unknown adapter %s. It won't be enabled.\n", cat, useadapter);
					break;
				}
				if ((pvt = ast_calloc(1, sizeof(*pvt)))) {
					if (type && !strcmp(type, "headset"))
						pvt->type = MBL_TYPE_HEADSET;
					else
						pvt->type = MBL_TYPE_PHONE;
					ast_copy_string(pvt->id, cat, sizeof(pvt->id));
					str2ba(address, &pvt->addr);
					ast_copy_string(pvt->context, S_OR(context, "default"), sizeof(pvt->context));
					if (group)
						pvt->group = atoi(group);	/* group 0 if invalid */
					pvt->state = MBL_STATE_INIT;
					pvt->rfcomm_socket = -1;
					pvt->rfcomm_port = atoi(port);
					pvt->sco_socket = -1;
					pvt->monitor_thread = AST_PTHREADT_NULL;
					pvt->sco_listener_thread = AST_PTHREADT_NULL;
					if (!ast_strlen_zero(nocallsetup)) {
						if ((*nocallsetup == 'y') || (*nocallsetup == 'Y')) {
							pvt->no_callsetup = 1;
							ast_log(LOG_DEBUG, "Setting nocallsetup mode for device %s.\n", pvt->id);
						}
					}
					pvt->dsp = ast_dsp_new();
					if (skip) {
						if ((pvt->dtmf_skip = atoi(skip)) == 0)
							pvt->dtmf_skip = 200;
					} else
						pvt->dtmf_skip = 200;
					ast_dsp_set_features(pvt->dsp, DSP_FEATURE_DTMF_DETECT);
					ast_dsp_digitmode(pvt->dsp, DSP_DIGITMODE_DTMF | DSP_DIGITMODE_RELAXDTMF);
					pvt->adapter = adapter;
					AST_LIST_INSERT_HEAD(&devices, pvt, entry);
				}
			} else {
				ast_log(LOG_ERROR, "Device %s has no address/port/adapter configured. It won't be enabled.\n", cat);
			}
		}
		cat = ast_category_browse(cfg, cat);
	}

	ast_config_destroy(cfg);

	return 1;

}

static int unload_module(void)
{

	struct mbl_pvt *pvt;
	struct adapter_pvt *adapter;

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

	/* Kill the discovery thread */
	if (discovery_thread != AST_PTHREADT_NULL) {
		pthread_cancel(discovery_thread);
		pthread_join(discovery_thread, NULL);
	}

	/* 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_listener_thread != AST_PTHREADT_NULL) {
			pthread_cancel(pvt->sco_listener_thread);
			pthread_join(pvt->sco_listener_thread, NULL);
		}
		if (pvt->sco_socket > -1) {
			close(pvt->sco_socket);
		}
		if (pvt->adapter->sco_socket > -1) {
			close(pvt->adapter->sco_socket);
		}
		if (pvt->rfcomm_socket > -1) {
			close(pvt->rfcomm_socket);
		}
		ast_dsp_free(pvt->dsp);
		ast_free(pvt);
	}

	/* Destroy the adapter list */
	while ((adapter = AST_LIST_REMOVE_HEAD(&adapters, entry))) {
		hci_close_dev(adapter->hci_socket);
		ast_free(adapter);
	}

	if (sdp_session)
		sdp_close(sdp_session);

	/* Unregister the CLI & APP */
	ast_cli_unregister_multiple(mbl_cli, sizeof(mbl_cli) / sizeof(mbl_cli[0]));
	ast_unregister_application(app_mblstatus);
	ast_unregister_application(app_mblsendsms);

	return 0;

}

static int load_module(void)
{

	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");
		return AST_MODULE_LOAD_DECLINE;
	}

	hci_close_dev(s);

	if (!mbl_load_config()) {
		ast_log(LOG_ERROR, "Unable to read config file %s. Not loading module.\n", MBL_CONFIG);
		return AST_MODULE_LOAD_DECLINE;
	}

	sdp_session = sdp_register();

	/* Spin the discovery thread */
	if (ast_pthread_create_background(&discovery_thread, NULL, do_discovery, NULL) < 0) {
		ast_log(LOG_ERROR, "Unable to create discovery thread.\n");
		return AST_MODULE_LOAD_DECLINE;
	}

	ast_cli_register_multiple(mbl_cli, sizeof(mbl_cli) / sizeof(mbl_cli[0]));
	ast_register_application(app_mblstatus, mbl_status_exec, mblstatus_synopsis, mblstatus_desc);
	ast_register_application(app_mblsendsms, mbl_sendsms_exec, mblsendsms_synopsis, mblsendsms_desc);

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

	return 0;
}

AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_DEFAULT, "Bluetooth Mobile Device Channel Driver",
		.load = load_module,
		.unload = unload_module,
);

