/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2001-2002  Nokia Corporation
 *  Copyright (C) 2002-2003  Maxim Krasnyansky <maxk@qualcomm.com>
 *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
 *  Copyright (C) 2002-2003  Stephen Crane <steve.crane@rococosoft.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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/socket.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/l2cap.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>

#include "sdpd.h"
#include "log.h"
#include "adapter.h"

static sdp_list_t *service_db;
static sdp_list_t *access_db;

typedef struct {
	uint32_t handle;
	bdaddr_t device;
} sdp_access_t;

/*
 * Ordering function called when inserting a service record.
 * The service repository is a linked list in sorted order
 * and the service record handle is the sort key
 */
int record_sort(const void *r1, const void *r2)
{
	const sdp_record_t *rec1 = r1;
	const sdp_record_t *rec2 = r2;

	if (!rec1 || !rec2) {
		error("NULL RECORD LIST FATAL");
		return -1;
	}

	return rec1->handle - rec2->handle;
}

static int access_sort(const void *r1, const void *r2)
{
	const sdp_access_t *rec1 = r1;
	const sdp_access_t *rec2 = r2;

	if (!rec1 || !rec2) {
		error("NULL RECORD LIST FATAL");
		return -1;
	}

	return rec1->handle - rec2->handle;
}

static void access_free(void *p)
{
	free(p);
}

/*
 * Reset the service repository by deleting its contents
 */
void sdp_svcdb_reset(void)
{
	sdp_list_free(service_db, (sdp_free_func_t) sdp_record_free);
	service_db = NULL;

	sdp_list_free(access_db, access_free);
	access_db = NULL;
}

typedef struct _indexed {
	int sock;
	sdp_record_t *record;
} sdp_indexed_t;

static sdp_list_t *socket_index;

/*
 * collect all services registered over this socket
 */
void sdp_svcdb_collect_all(int sock)
{
	sdp_list_t *p, *q;

	for (p = socket_index, q = 0; p; ) {
		sdp_indexed_t *item = p->data;
		if (item->sock == sock) {
			sdp_list_t *next = p->next;
			sdp_record_remove(item->record->handle);
			sdp_record_free(item->record);
			free(item);
			if (q)
				q->next = next;
			else
				socket_index = next;
			free(p);
			p = next;
		} else if (item->sock > sock)
			return;
		else {
			q = p;
			p = p->next;
		}
	}
}

void sdp_svcdb_collect(sdp_record_t *rec)
{
	sdp_list_t *p, *q;

	for (p = socket_index, q = 0; p; q = p, p = p->next) {
		sdp_indexed_t *item = p->data;
		if (rec == item->record) {
			free(item);
			if (q)
				q->next = p->next;
			else
				socket_index = p->next;
			free(p);
			return;
		}
	}
}

static int compare_indices(const void *i1, const void *i2)
{
	const sdp_indexed_t *s1 = i1;
	const sdp_indexed_t *s2 = i2;
	return s1->sock - s2->sock;
}

void sdp_svcdb_set_collectable(sdp_record_t *record, int sock)
{
	sdp_indexed_t *item = malloc(sizeof(sdp_indexed_t));
	item->sock = sock;
	item->record = record;
	socket_index = sdp_list_insert_sorted(socket_index, item, compare_indices);
}

/*
 * Add a service record to the repository
 */
void sdp_record_add(const bdaddr_t *device, sdp_record_t *rec)
{
	struct btd_adapter *adapter;
	sdp_access_t *dev;

	SDPDBG("Adding rec : 0x%lx", (long) rec);
	SDPDBG("with handle : 0x%x", rec->handle);

	service_db = sdp_list_insert_sorted(service_db, rec, record_sort);

	dev = malloc(sizeof(*dev));
	if (!dev)
		return;

	bacpy(&dev->device, device);
	dev->handle = rec->handle;

	access_db = sdp_list_insert_sorted(access_db, dev, access_sort);

	if (bacmp(device, BDADDR_ANY) == 0) {
		adapter_foreach(adapter_service_insert, rec);
		return;
	}

	adapter = adapter_find(device);
	if (adapter)
		adapter_service_insert(adapter, rec);
}

static sdp_list_t *record_locate(uint32_t handle)
{
	if (service_db) {
		sdp_list_t *p;
		sdp_record_t r;

		r.handle = handle;
		p = sdp_list_find(service_db, &r, record_sort);
		return p;
	}

	SDPDBG("Could not find svcRec for : 0x%x", handle);
	return NULL;
}

static sdp_list_t *access_locate(uint32_t handle)
{
	if (access_db) {
		sdp_list_t *p;
		sdp_access_t a;

		a.handle = handle;
		p = sdp_list_find(access_db, &a, access_sort);
		return p;
	}

	SDPDBG("Could not find access data for : 0x%x", handle);
	return NULL;
}

/*
 * Given a service record handle, find the record associated with it.
 */
sdp_record_t *sdp_record_find(uint32_t handle)
{
	sdp_list_t *p = record_locate(handle);

	if (!p) {
		SDPDBG("Couldn't find record for : 0x%x", handle);
		return 0;
	}

	return p->data;
}

/*
 * Given a service record handle, remove its record from the repository
 */
int sdp_record_remove(uint32_t handle)
{
	sdp_list_t *p = record_locate(handle);
	sdp_record_t *r;
	sdp_access_t *a;

	if (!p) {
		error("Remove : Couldn't find record for : 0x%x", handle);
		return -1;
	}

	r = p->data;
	if (r)
		service_db = sdp_list_remove(service_db, r);

	p = access_locate(handle);
	if (p == NULL || p->data == NULL)
		return 0;

	a = p->data;

	if (bacmp(&a->device, BDADDR_ANY) != 0) {
		struct btd_adapter *adapter = adapter_find(&a->device);
		if (adapter)
			adapter_service_remove(adapter, r);
	} else
		adapter_foreach(adapter_service_remove, r);

	access_db = sdp_list_remove(access_db, a);
	access_free(a);

	return 0;
}

/*
 * Return a pointer to the linked list containing the records in sorted order
 */
sdp_list_t *sdp_get_record_list(void)
{
	return service_db;
}

int sdp_check_access(uint32_t handle, bdaddr_t *device)
{
	sdp_list_t *p = access_locate(handle);
	sdp_access_t *a;

	if (!p)
		return 1;

	a = p->data;
	if (!a)
		return 1;

	if (bacmp(&a->device, device) &&
			bacmp(&a->device, BDADDR_ANY) &&
			bacmp(device, BDADDR_ANY))
		return 0;

	return 1;
}

uint32_t sdp_next_handle(void)
{
	uint32_t handle = 0x10000;

	while (sdp_record_find(handle))
		handle++;

	return handle;
}

void sdp_init_services_list(bdaddr_t *device)
{
	sdp_list_t *p;

	DBG("");

	for (p = access_db; p != NULL; p = p->next) {
		sdp_access_t *access = p->data;
		sdp_record_t *rec;

		if (bacmp(BDADDR_ANY, &access->device))
			continue;

		rec = sdp_record_find(access->handle);
		if (rec == NULL)
			continue;

		SDPDBG("adding record with handle %x", access->handle);

		adapter_foreach(adapter_service_insert, rec);
	}
}
