/* 
 * sdp.c -- Implementation of Bluetooth Service Discovery Protocol 
 * 
 * Copyright (C) 2000  Axis Communications AB
 *
 * Mats Friden <mats.friden@axis.com>
 *
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * $Id: sdp.c,v 1.45 2000/10/27 15:09:59 mattias Exp $
 *
 */

#define __NO_VERSION__ /* don't define kernel_version in module.h */

#ifdef __KERNEL__
#include <linux/malloc.h>
#include <linux/bluetooth/btcommon.h>
#include <linux/bluetooth/sdp.h>
#include <linux/bluetooth/sdp_proc_protocol.h>
#include <linux/bluetooth/l2cap.h>
#include <linux/bluetooth/bluetooth.h>
#include <linux/bluetooth/btmem.h>
#include <linux/proc_fs.h>
#else /* user mode */
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <syslog.h>
#include "include/bluetooth.h"
#include "include/btcommon.h"
#include "include/sdp.h"
#include "include/l2cap.h"
#include "include/btmem.h"
#include "include/sdp_proc_protocol.h"
#endif

/****************** DEBUG CONSTANT AND MACRO SECTION ************************/

#if SDP_DEBUG_XMIT
#define D_XMIT(fmt, args...) printk(SDP_DBG_STR fmt, ## args)
#else
#define D_XMIT(fmt, args...)
#endif

#if SDP_DEBUG_REC
#define D_REC(fmt, args...) printk(SDP_DBG_STR fmt, ## args)
#else
#define D_REC(fmt, args...)
#endif

#if SDP_DEBUG_MISC
#define D_MISC(fmt, args...) printk(SDP_DBG_STR fmt, ## args)
#else
#define D_MISC(fmt, args...)
#endif

#if SDP_DEBUG_MEM
#define D_MEM(fmt, args...) printk(fmt, ## args)
#else
#define D_MEM(fmt, args...)
#endif

#if SDP_DEBUG_PROC
#define D_PROC(fmt, args...) printk(fmt, ## args)
#else
#define D_PROC(fmt, args...)
#endif

#if SDP_PRINT_DATA
#define PRINTPKT(str, data, len) print_data(str, data, len)
#else
#define PRINTPKT(str, data, len)
#endif

/****************** CONSTANT AND MACRO SECTION ******************************/

#ifndef __KERNEL__
#define SDP_SRV_SOCK "/tmp/sdp_sock"
#endif

#define GET_TYPE(ch) (((ch) >> 3) & 0x1f)
#define GET_SIZE(ch) ((ch) & 0x7)
#define SET_DE_HDR(type,size) ((((type) << 3) & 0xf8) + ((size) & 0x7))

#define SDP_HDR_SIZE 5
/* The size of the SDP packet header */

#define DES_HDR 0x35
#define DES_HDR_LEN 2

#define UUID16_HDR 0x19
#define UUID32_HDR 0x1a

/* Maximum record counts */
#define MAX_SERVICERECORD_COUNT 8
#define MAX_ATTR_COUNT 0x100

/* Values used for the L2CAP configuration process */
#define SDP_MTU 672 
/* Default, why should we be different */
#define SDP_QOS 0x01 
/* Best effort */
#define SDP_FLUSH_TIMEOUT 0xffff 
/* Reliable channel, infinite amout of retransmissions */
#define MAX_NBR_SDP 7
/* The maximum number of sdp connections */
/****************** TYPE DEFINITION SECTION *********************************/

enum sdp_states{SDP_DISCONNECTED, SDP_CONNECTING, SDP_CONNECTED};

typedef struct sdp_con{
	l2cap_con *l2cap;
	u32 initiator;
	enum sdp_states state;
	u32 id;
}sdp_con;

typedef struct attr_id{
	u32 size:3; /* Least significant comes first in struct */
	u32 type:5;
	u8 data[0];
} attr_id;

typedef struct uuid_struct{
	u32 len;
	u8 *uuid;
}uuid_struct;

typedef struct attr_struct{
	u8 range;
	u8 *attr;
}attr_struct;

/****************** LOCAL FUNCTION DECLARATION SECTION **********************/
static void process_service_search_req(sdp_con *sdp, u8 *data, 
                                u16 len, u32 trans_id);
static void process_service_attr_req(sdp_con *sdp, u8 *data,
                              u16 len, s32 trans_id);
static void process_service_search_attr_req(sdp_con *sdp, u8 *data,
                              u16 len, s32 trans_id);

static void process_service_search_rsp(sdp_con *sdp, u8 *data);
static void process_service_attr_rsp(sdp_con *sdp, u8 *data);
static void process_service_search_attr_rsp(sdp_con *sdp, u8 *data);

static s32 send_error_rsp(sdp_con *sdp, u16 trans_id, u16 err_code);

static s32 get_size(u8 *data, u32 *new_pos);
static sdp_con* get_free_sdp_con(void);

#ifdef __KERNEL__
s32 sdp_database_read(struct inode *inode, struct file * file,
		       char * buf, int count);
s32 sdp_database_write(struct inode *inode, struct file * file,
		       const char * buf, int count);
s32 sdp_proc_dir_entry_read(char *buf, char **start, off_t offset, s32 len,
			    s32 unused);
#else
int open_socket(char *name);
s32 sdp_doquery(s32 fd, u8 *request, s32 len);
#endif



/****************** GLOBAL VARIABLE DECLARATION SECTION *********************/

/****************** LOCAL VARIABLE DECLARATION SECTION **********************/

#ifdef __KERNEL__
struct wait_queue *database_wq = NULL;

static struct file_operations sdp_procfile_operation = {
	NULL,                   /* lseek - default */
	sdp_database_read,      /* read - bad */
	sdp_database_write,     /* write - bad */
	NULL,                   /* readdir */
	NULL,                   /* select - default */
	NULL,                   /* ioctl - default */
	NULL,                   /* mmap */
	NULL,                   /* no special open code */
	NULL,                   /* no special release code */
	NULL                    /* can't fsync */
};
 
 /*
  * proc directories can do almost nothing..
  */

struct inode_operations sdp_proc_inode_operations = {
	&sdp_procfile_operation,   /* default net file-ops */
	NULL,                   /* create */
	NULL,                   /* lookup */
	NULL,                   /* link */
	NULL,                   /* unlink */
	NULL,                   /* symlink */
	NULL,                   /* mkdir */
	NULL,                   /* rmdir */
	NULL,                   /* mknod */
	NULL,                   /* rename */
	NULL,                   /* readlink */
	NULL,                   /* follow_link */
	NULL,                   /* readpage */
	NULL,                   /* writepage */
	NULL,                   /* bmap */
	NULL,                   /* truncate */
	NULL                    /* permission */
};

struct proc_dir_entry sdp_proc_entry = {
	0,
	7, "sdp_srv",
	S_IFREG | S_IRUGO | S_IWUSR,
	1, 0, 0,
	0,
	&sdp_proc_inode_operations,
	sdp_proc_dir_entry_read,
};

#endif /* __KERNEL__ */

struct database_query {
	u32 count;
	u8 query[256];
} database_query;

static sdp_con sdp_con_list[7];

/* Transaction id used for request */
//static u16 req_trans_id = 0xaabb;

#ifndef __KERNEL__
static s32 sdp_sock;
#endif
/****************** FUNCTION DEFINITION SECTION *****************************/

#ifndef __KERNEL__
int
open_socket(char *name)
{
  struct sockaddr_un server_address;
  int client_sockfd;
  int server_len;
  
  printf("Opening socket %s\n", name);
  
  client_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);

  /* 'destination' socket */
  server_address.sun_family = AF_UNIX;
  strcpy(server_address.sun_path, name);
  server_len = sizeof(server_address);
  
  if (connect(client_sockfd, 
	      (struct sockaddr *)&server_address, server_len) < 0)
  {
	  perror("connect client socket");
	  D_ERR("Couldn't open socket, sdp will not be used\n");
	  return -1;
  }

  printf("Socket connected to %s\n", server_address.sun_path);
  return client_sockfd;
}
#endif

void 
sdp_init(void)
{
	s32 i;
	protocol_layer this_layer;
	DSYS("Initialising SDP\n");

	database_query.count = 0;
	
	/* Set the confirm and indication functions for the L2CAP-layer */
	this_layer.con_ind = sdp_connect_ind;
	this_layer.conf_ind = sdp_config_ind;
	this_layer.disc_ind = sdp_disconnect_ind;

	this_layer.con_cfm = sdp_connect_cfm;
	this_layer.conf_cfm = sdp_config_cfm;
	this_layer.disc_cfm = sdp_disconnect_cfm;

	this_layer.receive_data = sdp_receive_data;

	/* Register SDP in the L2AP layer*/
	l2cap_register_upper(SDP_LAYER, &this_layer); 
	for(i = 0; i < 7; i++) {
		sdp_con_list[i].id = i;
		sdp_con_list[i].l2cap = NULL;
		sdp_con_list[i].state = SDP_DISCONNECTED;
		sdp_con_list[i].initiator = 0;
	}

#ifndef __KERNEL__
	sdp_sock = open_socket(SDP_SRV_SOCK);
#endif
}

void 
sdp_shutdown(void)
{
	s32 i;
	DSYS("Shutting down SDP\n");

	for (i = 0; i < 7; i++) {
		sdp_con_list[i].l2cap = NULL;
		sdp_con_list[i].state = SDP_DISCONNECTED;
	}  
}

s32 
sdp_connect_req(u8* bd_addr)
{
#define FNC "sdp_connect_req: "
	sdp_con *sdp;
	s32 error = -1;
  
	sdp = get_free_sdp_con();
	if (!sdp) {
		DSYS(FNC"WARNING couldn't find free sdp connection\n");
		bt_sdp_connection_ready(error);
		return 0;
	}
	sdp->state = SDP_CONNECTING;
	sdp->initiator = TRUE;
	
	if (l2ca_connect_req(bd_addr, SDP_LAYER)) {
		D_ERR(FNC"ERROR l2ca_connect_req failed\n");
		return 0;
	}
	return sdp->id;
#undef FNC
}

void 
sdp_connect_ind(l2cap_con *l2cap) 
{
#define FNC "sdp_connect_ind: "
	sdp_con *sdp;
	D_MISC(FNC"remote cid : %d\n", l2cap->remote_cid);
   
	sdp = get_free_sdp_con();
	if (!sdp) {
		DSYS(FNC"WARNING couldn't find free sdp connection\n");
		if (l2ca_connect_rsp(l2cap, RES_NOSRC, STAT_NOINFO)) {
			D_ERR(FNC"l2ca_connect_rsp failed\n");
		}
	} else {
		if (l2ca_connect_rsp(l2cap, RES_SUCCESS, STAT_NOINFO)) {
			D_ERR(FNC"l2ca_connect_rsp failed\n"); 
		}
		sdp->initiator = FALSE;
		sdp->state = SDP_CONNECTING;
		sdp->l2cap = l2cap;
		l2cap->upper_con = (void*) sdp;
	}
#undef FNC
}

void 
sdp_connect_cfm(l2cap_con *l2cap, s32 status)
{
#define FNC "sdp_connect_cfm: "
	sdp_con *sdp = NULL;
	s32 error = -1;
	s32 i = 0;
	s32 stop = 0;
  
	if (status) {
		DSYS(FNC"Connection failed\n");
		bt_sdp_connection_ready(error);
		return;
	}

	/* Find the connecting sdp_con */
	while ((i < MAX_NBR_SDP) && (!stop)) {
		if ((sdp_con_list[i].state == SDP_CONNECTING) && 
		    (sdp_con_list[i].initiator == TRUE)) {
			sdp = &sdp_con_list[i];
			stop = TRUE;
		}
		i++;
	}
  
	if (sdp != NULL) {

		if ( !l2ca_local_conf_done(l2cap) )
		{
			/* still haven't sent config request yet */
			if (l2ca_config_req(l2cap, SDP_MTU,NULL,
					    SDP_FLUSH_TIMEOUT,0)) {
				D_ERR(FNC"Configuration request failed\n");
			}
			/* store connection */
			sdp->l2cap = l2cap;
			l2cap->upper_con = (void*) sdp;
		} else 
			DSYS(FNC"already sent back a pos response\n");
	} else {
		D_ERR(FNC"couldn't find the correct sdp_connection\n");
		bt_sdp_connection_ready(error);
	}
#undef FNC
}

/* Sends back info about  MTU, outflow, flush timeout
   Get this data from some parameter area controlled by 
   control block ? */

void 
sdp_config_ind(l2cap_con* l2cap)
{
#define FNC "sdp_config_ind: "
	D_MISC(FNC"remote cid : %d\n", l2cap->remote_cid);

	/* 
	   FIXME -- Check whether the received params are 
	   acceptable, accept all for now
	*/

	/* check if we have sent a pos response yet */
	if (!l2ca_remote_conf_done(l2cap)){
		
		/* still haven't sent a pos configure response*/
		{
		  /* remote options is set in l2cap layer and contains values 
		     for the config req (parse_options) */

			if (l2ca_config_rsp(l2cap, l2cap->remote_mtu,
					    NULL,1)) {
				D_ERR(FNC"Conf rsp failed\n");
			}
		}    
	} else 
		DSYS("already have sent back a pos response\n");


	/* check if we received a pos response on a 
	   previous config req */ 
	if (!l2ca_local_conf_done(l2cap))
	{
		/* up3 -- fixme, use real options not static values */
		
		if (l2ca_config_req(l2cap, SDP_MTU, NULL, 
				    SDP_FLUSH_TIMEOUT, 0)) {
			D_ERR(FNC"Config request failed\n");
		}  
	} else 
		D_REC("already ready with config req\n");

#undef FNC
}

void 
sdp_config_cfm(l2cap_con *l2cap, s32 status)
{
#define FNC "sdp_config_cfm: "

	D_MISC(FNC"remote cid : %d\n", l2cap->remote_cid);

	/* negotiate SDP if client */
	if (((sdp_con*)l2cap->upper_con)->initiator) {
		/* Here we should start sending sdp queries */
	} else {
		DSYS(FNC"we are server\n");
	}

#undef FNC
}

void 
sdp_disconnect_req(u32 sdp_hdl)
{
	sdp_con *sdp;
	sdp = (sdp_con*) sdp_hdl;
  
	l2ca_disconnect_req(sdp->l2cap);
}  

void 
sdp_disconnect_ind(l2cap_con *l2cap) 
{
#define FNC "sdp_disconnect_ind: "
	D_MISC(FNC"remote cid : %d\n", l2cap->remote_cid);
   
	((sdp_con*)l2cap->upper_con)->state = SDP_DISCONNECTED;
	((sdp_con*)l2cap->upper_con)->l2cap = NULL;
  

	l2ca_disconnect_rsp(l2cap);
#undef FNC
}


void 
sdp_disconnect_cfm(l2cap_con *l2cap)
{
#define FNC "sdp_disconnect_cfm: "
	sdp_con *sdp;
	D_MISC(FNC"remote cid : %d\n", l2cap->remote_cid);

	sdp = (sdp_con*) l2cap->upper_con;
	sdp->state = SDP_DISCONNECTED;
	sdp->l2cap = NULL;  
#undef FNC
}

void 
sdp_receive_data(l2cap_con *l2cap, u8* data, u32 len)
{
#define FNC "sdp_receive_data: "
	u8 id;
	u16 trans_id;
	u16 par_len;
	sdp_con *sdp;
  
	PRINTPKT(FNC, data, len);  
	sdp = (sdp_con*) l2cap->upper_con;

	id = data[0];
	trans_id = CHAR2INT16(data[1], data[2]);
	par_len = CHAR2INT16(data[3], data[4]);
  
	D_REC(FNC"id:0x%x, trans_id:0x%x, par_len:%d\n", id, trans_id,par_len);

#ifndef __KERNEL__
	if (sdp_sock < 0) {
		send_error_rsp(sdp, trans_id, 6);
	}
#endif	
	/* Break out into separate info function ? */
	switch (id) {
	case SDP_ERROR_RSP:
		switch (CHAR2INT16(data[5], data[6])) {
		case 0x0001: D_ERR(FNC" Invalid/unsupported SDP version\n");
			break;
		case 0x0002: D_ERR(FNC"Invalid Service Record Handle\n");
			break;
		case 0x0003: D_ERR(FNC"Invalid request syntax\n");
			break;
		case 0x0004: D_ERR(FNC"Invalid PDU Size\n");
			break;
		case 0x0005: D_ERR(FNC"Invalid Continuation State\n");
			break;
		case 0x0006: D_ERR(FNC"Insufficient Resources to satisfy Request\n");
			break;
		default:
			D_ERR(FNC" Error code 0x%04x is not specified\n",
			      CHAR2INT16(data[5], data[6]));
			break;
		}
		break;
		
	case SDP_SERVICESEARCH_REQ:
		D_REC(FNC"SDP_SERVICESEARCH_REQ %d bytes\n", par_len);
		process_service_search_req(sdp, data + 5, par_len, trans_id);
		break;
    
	case SDP_SERVICESEARCH_RSP:
		D_REC(FNC"SDP_SERVICESEARCH_RSP\n");
		process_service_search_rsp(sdp, data);
		break;
    
	case SDP_SERVICEATTR_REQ:
		D_REC(FNC"SDP_SERVICEATTR_REQ\n");
		process_service_attr_req(sdp, data + 5, par_len, trans_id);
		sdp->state = SDP_CONNECTED;
		break;
    
	case SDP_SERVICEATTR_RSP:
		D_REC(FNC"SDP_SERVICEATTR_RSP\n");
		process_service_attr_rsp(sdp, data);
		bt_sdp_connection_ready(0);
		break;
    
	case SDP_SERVICESEARCHATTR_REQ:
		D_REC(FNC"SDP_SERVICESEARCHATTR_REQ\n");
		process_service_search_attr_req(sdp, data + 5, par_len,
						trans_id);
		break;
    
	case SDP_SERVICESEARCHATTR_RSP:
		D_REC(FNC"SDP_SERVICESEARCHATTR_RSP\n");
		process_service_search_attr_rsp(sdp, data);
		break;
    
	default:
		D_REC(FNC"ERROR Invalid pdu type\n");
		/* Send an error response with error code Invalid request syntax */
		send_error_rsp(sdp, trans_id, 3);
		break;
	}
#undef FNC
}

void
process_service_search_req(sdp_con *sdp, u8 *data, u16 len, u32 trans_id)
{
#define FNC "process_service_search_req: "
	service_search_struct *db_hdl;
	
	u32 service_search_uuid[12];
	s32 service_search_uuid_cnt;
	u8 des_len;
	s32 tmp_pos, i, cur_pos = 0;
	s32 max_rec_cnt;
	s32 new_pos[1];
	
	D_REC(FNC"got %d bytes\n", len);
	if (!data[cur_pos] == DES_HDR) {
		D_ERR(FNC"Incorrect packet: Data Element Sequence expected\n");
		/* Send an error msg with error code Invalid request syntax */
		send_error_rsp(sdp, trans_id, 3);
		return;
	}

	des_len = get_size(data + cur_pos, new_pos);
	cur_pos += *new_pos;
	D_MISC(FNC"des_len: %d, new_pos:%d\n",des_len, *new_pos);
	
	if (des_len > (len - DES_HDR_LEN)) {
		D_ERR(FNC"Incorrect packet: Incorrect length field or whole packet was not received\n");
		/* Send an error msg with error code Invalid request syntax */
		send_error_rsp(sdp, trans_id, 3);
		return;
	}
	
	tmp_pos = 0;
	i = 0;
	while (tmp_pos < des_len) {
		if (i >= 12) {
			D_ERR(FNC"More than 12 UUID in one request\n");
			/* Send an error msg with error code Invalid request
			   syntax */
			send_error_rsp(sdp, trans_id, 3);
			break;
		}
		
		if (data[cur_pos] == UUID16_HDR) {
			service_search_uuid[i] = CHAR2INT16(data[cur_pos + 1],
							    data[cur_pos + 2]);
			D_MISC(FNC"Found UUID16 0x%04x\n",
			       service_search_uuid[i]); 
			i += 1;
			tmp_pos += 3;
			cur_pos += 3;
		} else if (data[cur_pos] == UUID32_HDR) {
			service_search_uuid[i] = CHAR2INT16(data[cur_pos + 1],
							    data[cur_pos + 2]);
			D_MISC(FNC"Found UUID32 0x%08x\n",
			       service_search_uuid[i]); 
			i += 1;
			tmp_pos += 5;
			cur_pos += 5;
		} else {
			D_ERR(FNC"Unknown UUID size 0x%02x\n",data[cur_pos]);
			/* Send an error msg with error code Invalid request
			   syntax */
			send_error_rsp(sdp, trans_id, 3);
			return;
		}
	}
	service_search_uuid_cnt = i;

	max_rec_cnt = CHAR2INT16(data[cur_pos], data[cur_pos + 1]);
  	D_MISC(FNC"max_rec_cnt: %d\n", max_rec_cnt);
	cur_pos += 2;

	len = sizeof(service_search_struct);
	
	db_hdl = (service_search_struct*) kmalloc(len, GFP_ATOMIC);
	D_MEM("---> kmalloc %d bytes at 0x%08x\n", len, (s32) db_hdl);
	
	db_hdl->db.sdp_con_id = sdp->id;
	db_hdl->db.trans_id = trans_id;
	db_hdl->db.pkt_type = SDP_SERVICESEARCH_REQ;
	
	db_hdl->max_rec_cnt = max_rec_cnt;
	db_hdl->service_class_cnt = service_search_uuid_cnt;
	memcpy(db_hdl->service_class_list, service_search_uuid,
	       service_search_uuid_cnt * 4);

	/* Here we ask the database for the requested attributes */

	database_query.count = len;
	memcpy(database_query.query, db_hdl, len);

	D_MEM("<--- kfree 0x%08x\n", (s32) db_hdl);
	kfree(db_hdl);

#ifdef __KERNEL__
	wake_up_interruptible(&database_wq);
#else
	sdp_doquery(sdp_sock, (u8*) &database_query.query, len);
#endif
	
#undef FNC
}

void 
process_service_attr_req(sdp_con *sdp, u8 *data, u16 len, s32 trans_id)
{
#define FNC "process_service_attr_req: "
	service_attr_struct *db_hdl;
	
	s32 tmp_len, cur_pos = 0;
	u8 des_len;
	
	u32 rec_hdl;
	u32 max_attr_cnt;
	u32 attr_list[256];
	s32 attr_list_pos = 0;

	s32 cont_state_len;
  
	s32 new_pos[1];

	D_REC(FNC"got %d bytes\n", len);

	rec_hdl = CHAR2INT32(data[cur_pos], data[cur_pos + 1],
			     data[cur_pos  + 2], data[cur_pos + 3]);

	D_MISC(FNC"rec_hdl: 0x%08x\n",rec_hdl);
	cur_pos += 4;

	max_attr_cnt = CHAR2INT16(data[cur_pos], data[cur_pos + 1]);
  	D_MISC(FNC"max_attr_cnt: %d\n",max_attr_cnt);
	cur_pos += 2;

	if (data[cur_pos] != DES_HDR) {
		D_ERR(FNC"Incorrect packet: Data Element Sequence expected\n");
		/* Send an error msg with error code Invalid request syntax */
		send_error_rsp(sdp, trans_id, 3);
		return;
	}

	des_len = get_size(data + cur_pos, new_pos);
	cur_pos += *new_pos;
	D_MISC(FNC"des_len: %d, new_pos:%d\n",des_len, *new_pos);
	
	if (des_len > (len - DES_HDR_LEN)) {
		D_ERR(FNC"Incorrect packet: Incorrect length field or whole packet was not received\n");
		/* Send an error msg with error code Invalid request syntax */
		send_error_rsp(sdp, trans_id, 3);
		return;
	}

	/* Now we parses the third parameter which is a data element list. i.e
	   we have to go through the whole list and pick out one element at
	   time */
	while (des_len > 0) {
		/* Get the length of the data element */
		tmp_len = get_size(data + cur_pos, new_pos);
		D_MISC(FNC"tmp_len: %d, new_pos: %d\n", tmp_len, *new_pos);
		/* Move the data pointer past the length field to the start of
		   the data element */
		cur_pos += *new_pos;
		if (tmp_len == 4) {
			/* Now we got a range of attributes */
			attr_list[attr_list_pos] = CHAR2INT32(data[cur_pos], data[cur_pos + 1], data[cur_pos  + 2], data[cur_pos + 3]);
			D_MISC(FNC"Found range of attributes: 0x%08x\n",
			       attr_list[attr_list_pos]); 
			cur_pos += 4;
			attr_list_pos += 1;
		} else {
			/* Just a single attributes */
			attr_list[attr_list_pos] = CHAR2INT16(data[cur_pos], data[cur_pos + 1]);
			/* We stor all attributes as ranges */
			attr_list[attr_list_pos] |= attr_list[attr_list_pos] << 16;
			D_MISC(FNC"Found single attributes: 0x%04x\n",
			       attr_list[attr_list_pos]); 
			cur_pos += 2;
			attr_list_pos += 1;	
		}
		des_len -= (*new_pos + tmp_len);
	}
	
	cont_state_len = data[cur_pos];

	/* FIXME: Here we have to check for the bytes in the continuation state
	   , but now we know that there shouldn't be any :) */

	len = sizeof(service_attr_struct) + attr_list_pos * 4;
	
	db_hdl = (service_attr_struct*) kmalloc(len, GFP_ATOMIC);
	D_MEM("---> kmalloc %d bytes at 0x%08x\n", len, (s32) db_hdl);
	
	db_hdl->db.sdp_con_id = sdp->id;
	db_hdl->db.trans_id = trans_id;
	db_hdl->db.pkt_type = SDP_SERVICEATTR_REQ;
	
	db_hdl->max_attr_byte_cnt = max_attr_cnt;
	db_hdl->rec_hdl = rec_hdl;
	db_hdl->attr_cnt = attr_list_pos;
	memcpy(db_hdl->attr_list, attr_list, attr_list_pos * 4);

	/* Here we ask the database for the requested attributes */

	database_query.count = len;
	memcpy(database_query.query, db_hdl, len);

	D_MEM("<--- kfree 0x%08x\n", (s32) db_hdl);
	kfree(db_hdl);
	
#ifdef __KERNEL__
	wake_up_interruptible(&database_wq);
#else
	sdp_doquery(sdp_sock, (u8*) &database_query.query, len);
#endif
	
#undef FNC
}

void 
process_service_search_attr_req(sdp_con *sdp, u8 *data, 
			 u16 len, s32 trans_id)
{
#define FNC "process_service_search_attr_req: "
	service_search_attr_struct *db_hdl;
	
	u32 service_search_uuid[12];
	s32 service_search_uuid_cnt;
	u8 des_len;
	s32 tmp_pos, tmp_len, i, cur_pos = 0;
	s32 max_attr_cnt;
	s32 new_pos[1];
	u32 attr_list[256];
	s32 attr_list_pos = 0;

	u8 cont_state_len;
	
	
	D_REC(FNC"got %d bytes\n", len);
	if (!data[cur_pos] == DES_HDR) {
		D_ERR(FNC"Incorrect packet: Data Element Sequence expected\n");
		return;
	}
	
	if (data[cur_pos] > (len - DES_HDR_LEN)) {
		D_ERR(FNC"Incorrect packet: Incorrect length field or whole packet was not received\n");
		return;
	} else {
		des_len = data[1];
	}
	cur_pos += 1;
	
	tmp_pos = 0;
	i = 0;
	while (tmp_pos < des_len) {
		if (i >= 12) {
			D_ERR(FNC"More than 12 UUID in one request\n");
			break;
		}
		
		if (data[cur_pos] == UUID16_HDR) {
			service_search_uuid[i] = CHAR2INT16(data[cur_pos + 1],
							    data[cur_pos + 2]);
			i += 1;
			tmp_pos += 3;
			cur_pos += 3;
		} else if (data[cur_pos] == UUID32_HDR) {
			service_search_uuid[i] = CHAR2INT16(data[cur_pos + 1],
							    data[cur_pos + 2]);
			i += 1;
			tmp_pos += 5;
			cur_pos += 5;
		} else {
			D_ERR(FNC"Unknown UUID size 0x%02x\n",data[cur_pos]);
			return;
		}
	}
	service_search_uuid_cnt = i;

	max_attr_cnt = CHAR2INT16(data[cur_pos], data[cur_pos + 1]);
  	D_MISC(FNC"max_attr_cnt: %d\n",max_attr_cnt);
	cur_pos += 2;

	D_REC(FNC"got %d bytes\n", len);
	if (!data[cur_pos] == DES_HDR) {
		D_ERR(FNC"Incorrect packet: Data Element Sequence expected\n");
		/* Send an error msg with error code Invalid request syntax */
		send_error_rsp(sdp, trans_id, 3);
		return;
	}
	cur_pos += 1;

	des_len = get_size(data + cur_pos, new_pos);
	cur_pos += *new_pos;
	D_MISC(FNC"des_len: %d, new_pos:%d\n",des_len, *new_pos);
	
	if (des_len > (len - DES_HDR_LEN)) {
		D_ERR(FNC"Incorrect packet: Incorrect length field or whole packet was not received\n");
		/* Send an error msg with error code Invalid request syntax */
		send_error_rsp(sdp, trans_id, 3);
		return;
	}
	
	/* Now we parses the third parameter which is a data element list. i.e
	   we have to go through the whole list and pick out one element at
	   time */
	while (des_len > 0) {
		/* Get the length of the data element */
		tmp_len = get_size(data + cur_pos, new_pos);
		D_MISC(FNC"tmp_len: %d, new_pos: %d\n", tmp_len, *new_pos);
		/* Move the data pointer past the length field to the start of
		   the data element */
		cur_pos += *new_pos;
		if (tmp_len == 4) {
			/* Now we got a range of attributes */
			attr_list[attr_list_pos] = CHAR2INT32(data[cur_pos], data[cur_pos + 1], data[cur_pos  + 2], data[cur_pos + 3]);
			cur_pos += 4;
			attr_list_pos += 1;
		} else {
			/* Just a single attributes */
			attr_list[attr_list_pos] = CHAR2INT16(data[cur_pos], data[cur_pos + 1]);
			cur_pos += 2;
			attr_list_pos += 1;	
		}
		des_len -= (*new_pos + tmp_len);
	}
	
	cont_state_len = data[cur_pos];

	/* FIXME: Here we have to check for the bytes in the continuation state
	   , but now we know that there shouldn't be any :) */
	
	/* FIXME: Here we should ask the database for the requested attributes,
	   we need to ask once for each attributes, exept if we want to ask for
	   a range of attributes. */

	tmp_len = sizeof(service_search_attr_struct) + attr_list_pos * 4;
	
	db_hdl = (service_search_attr_struct*) kmalloc(tmp_len, GFP_ATOMIC);
	D_MEM("---> kmalloc %d bytes at 0x%08x\n", tmp_len, (s32) db_hdl);
	
	db_hdl->db.sdp_con_id = sdp->id;
	db_hdl->db.trans_id = trans_id;
	db_hdl->db.pkt_type = SDP_SERVICESEARCHATTR_REQ;

	db_hdl->service_class_cnt = service_search_uuid_cnt;
	memcpy(db_hdl->service_class_list, service_search_uuid, service_search_uuid_cnt * 4);
	db_hdl->attr_cnt = attr_list_pos;
	memcpy(db_hdl->attr_list, attr_list, attr_list_pos * 4);
	
	/* Here we ask the database for the requested attributes */

	database_query.count = tmp_len;
	memcpy(database_query.query, db_hdl, tmp_len);

	D_MEM("<--- kfree 0x%08x\n", (s32) db_hdl);
	kfree(db_hdl);

#ifdef __KERNEL__
	wake_up_interruptible(&database_wq);
#else
	sdp_doquery(sdp_sock, (u8*) &database_query.query, len);
#endif
		
#undef FNC	
}

void
process_service_search_rsp(sdp_con *sdp, u8 *data)
{
#define FNC "process_service_search_rsp: "
	
#undef FNC	
}

void 
process_service_attr_rsp(sdp_con *sdp,u8 *data)
{
#define FNC "process_service_attr_rsp: "
	
#undef FNC
}

void
process_service_search_attr_rsp(sdp_con *sdp, u8 *data)
{
#define FNC "process_service_search_attr_rsp: "
	
#undef FNC	
}

s32
send_error_rsp(sdp_con *sdp, u16 trans_id, u16 err_code)
{
#define FNC "send_error_rsp: "
	sdp_tx_buf *sdp_buf;
	bt_tx_buf *tx_buf;
	u32 sdp_frame_len;
	u32 pdu_len;

	/* Since we not send any error information the pdu length is just the size
	   of the error code length, whoch is two bytes */
	pdu_len = 2;
	sdp_frame_len = SDP_HDR_SIZE + pdu_len;

	tx_buf = subscribe_bt_buf(sizeof(sdp_tx_buf) + sdp_frame_len);

	if (!tx_buf) {
		D_ERR(FNC"failed to get tx buffer\n");
		return -1;
	}

	tx_buf->cur_len = sdp_frame_len;
	
	sdp_buf = (sdp_tx_buf*) (tx_buf->data);

	sdp_buf->frame[0] = SDP_ERROR_RSP;
	sdp_buf->frame[1] = (trans_id >> 8) & 0xff;
	sdp_buf->frame[2] = trans_id & 0xff;
	sdp_buf->frame[3] = (pdu_len >> 8) & 0xff;
	sdp_buf->frame[4] = pdu_len & 0xff;

	sdp_buf->frame[5] = (err_code >> 8) & 0xff;
	sdp_buf->frame[6] = err_code & 0xff;
	
	return l2cap_send_data(tx_buf, sdp->l2cap);

#undef FNC
}

#ifdef __KERNEL__
s32 sdp_proc_dir_entry_read(char *buf, char **start, off_t offset, s32 len, s32 unused)
{
#define FNC "sdp_proc_dir_entry_read: "
	return sdp_database_read(NULL, NULL, buf, len);
#undef FNC
}

s32 sdp_database_read(struct inode *inode, struct file * file,
		       char * buf, int count)
{
#define FNC "sdp_database_read: "
	
	s32 tmp;
	
	D_PROC(FNC" Someone is trying to read % d bytes from sdp proc-file\n",
	       count);
	
	if (database_query.count <= 0) {
		D_PROC(FNC"No bytes availible, going to sleep\n");
		interruptible_sleep_on(&database_wq);
	}
	
	tmp = MIN(count, database_query.count);
	memcpy(buf, database_query.query, tmp);
	database_query.count -= tmp;
	D_PROC(FNC"Returning %d bytes\n",tmp);

	return tmp;
	
#undef FNC
}


s32 sdp_database_write(struct inode *inode, struct file * file,
		       const char * buf, int count)
{
#define FNC "sdp_database_write: "	
	sdp_tx_buf *sdp_buf;
	bt_tx_buf *tx_buf;
	data_struct *db_hdl = (data_struct*) buf;
	
	D_PROC(FNC"Someone wrote %d bytes to sdp proc-file\n",count);

	D_XMIT(FNC"preparing to send data to sdp_con[%d]\n", db_hdl->sdp_con_id);
	PRINTPKT("Data to be sent to client:", db_hdl->data, db_hdl->len);
	
	tx_buf = subscribe_bt_buf(sizeof(sdp_tx_buf) + db_hdl->len);

	if (!tx_buf) {
		D_ERR(FNC"failed to get tx buffer\n");
		return -1;
	}

	tx_buf->cur_len = db_hdl->len;
	sdp_buf = (sdp_tx_buf*) (tx_buf->data);

	memcpy(sdp_buf->frame, db_hdl->data, db_hdl->len);

	l2cap_send_data(tx_buf, sdp_con_list[db_hdl->sdp_con_id].l2cap);
	
	return count;
#undef FNC
}
#else /* __KERNEL__ */

s32 sdp_doquery(s32 fd, u8 *request, s32 len)
{
	s32 n;
	u8 tmpbuf[256];
	sdp_tx_buf *sdp_buf;
	bt_tx_buf *tx_buf;
	data_struct *db_hdl;
	
	syslog(LOG_INFO, "sdp_doquery : sending request\n");
	write(fd, request, len);
	
	n = read(fd, tmpbuf, 256);

	db_hdl = (data_struct*) tmpbuf;

	/* what if not all is written once */
	if (n < (sizeof(data_struct) + db_hdl->len))
	  fprintf(stderr, "sdp_doquery : only got partial response\n");
	  
	syslog(LOG_INFO, "sdp_doquery : got response [%d]\n", n);

	tx_buf = subscribe_bt_buf(sizeof(sdp_tx_buf) + db_hdl->len);

	if (!tx_buf) {
		D_ERR("sdp_doquery:  failed to get tx buffer\n");
		return -1;
	}

	tx_buf->cur_len = db_hdl->len;

	sdp_buf = (sdp_tx_buf*) (tx_buf->data);

	memcpy(sdp_buf->frame, db_hdl->data, db_hdl->len);

	l2cap_send_data(tx_buf, sdp_con_list[db_hdl->sdp_con_id].l2cap);
	
	return n;
}


#endif

/* "Support" functions used by the other functions in this file */

s32 
get_size(u8 *data, u32 *new_pos)
{
	s32 size_index;

	size_index = GET_SIZE(*data);
	switch (size_index) {
	case 0: 
		*new_pos = 1;
		return 1;
	case 1:
		*new_pos = 1;
		return 2;
	case 2:
		*new_pos = 1;
		return 4;
	case 3:
		*new_pos = 1;
		return 8;
	case 4:
		*new_pos = 1;
		return 16;
	case 5:
		*new_pos = 2;
		return *(data + 1);
	case 6:
		*new_pos = 3;
		return CHAR2INT16(data[1], data[2]);
	case 7:
		*new_pos = 5;
		return CHAR2INT32(data[1], data[2], data[3], data[4]);
	}
	return 0;
}

sdp_con* 
get_free_sdp_con(void)
{
	s32 i = 0;

	while ((i < 7) && (sdp_con_list[i].state != SDP_DISCONNECTED)) {
		i++;
	}

	if ((i == 7) && (sdp_con_list[i].state != SDP_DISCONNECTED)) {
		return 0;
	}
	return &sdp_con_list[i];
}

/****************** END OF FILE sdp.c ***************************************/

