/*
 * btscanner - Displays the output of Bluetooth scans
 * Copyright (C) 2003 Pentest Limited
 * 
 * Written 2003 by Tim Hurman <timh at pentest.co.uk>
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY
 * RIGHTS.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE
 * FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY
 * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 * ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS,
 * COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE
 * IS DISCLAIMED.
 */

/*
 * sdp.c: the formatting for for the SDP output.
 */

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

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif

#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif

#ifdef HAVE_STRING_H
#include <string.h>
#endif

#include <stdio.h>

#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>

#include <cfg.h>
#include <misc.h>
#include <ll.h>
#include <sdp.h>

static char UUID_str[MAX_LEN_UUID_STR];


/* start device classes */
#define CLASS_BITMASK 0
#define CLASS_CHOOSE 1

struct sub_classes
{
	uint32_t class_id;
	char *class_name;
};
typedef struct sub_classes sub_classes_t;

struct classes
{
	uint32_t class_id;
	uint32_t sub_major_mask;
	uint32_t sub_minor_mask;
	uint8_t type;
	sub_classes_t *sub_major;
	sub_classes_t *sub_minor;
	char *class_name;
};
typedef struct classes classes_t;

sub_classes_t class_computer[] = {
	{0x00, "Uncategorised"},
	{0x04, "Desktop"},
	{0x08, "Server"},
	{0x0c, "Laptop"},
	{0x10, "Handheld PC-PDA"},
	{0x14, "Palm sized PC-PDA"},
	{0x18, "Wearable computer"},
	{0x00, NULL}
};

sub_classes_t class_phone[] = {
	{0x00, "Uncategorised"},
	{0x04, "Mobile"},
	{0x08, "Cordless"},
	{0x0c, "Smart phone"},
	{0x10, "Wired modem or voice gateway"},
	{0x14, "Common ISDN Access"},
	{0x00, NULL}
};

sub_classes_t class_network[] = {
	{0x00, "0% utilised"},
	{0x20, "1 - 17% utilised"},
	{0x40, "17 - 33% utilised"},
	{0x60, "33 - 50% utilised"},
	{0x80, "50 - 67% utilised"},
	{0xa0, "67 - 83% utilised"},
	{0xc0, "83 - 99% utilised"},
	{0xe0, "100% utilised"},
	{0x00, NULL}
};

sub_classes_t class_av[] = {
	{0x00, "Uncategorised"},
	{0x04, "Headset"},
	{0x08, "Hands free"},
	{0x0c, "Reserved"},
	{0x10, "Microphone"},
	{0x14, "Loudspeaker"},
	{0x18, "Headphones"},
	{0x1c, "Portable Audio"},
	{0x20, "Car Audio"},
	{0x24, "Set top box"},
	{0x28, "HiFi Audio"},
	{0x2c, "VCR"},
	{0x30, "Video Camera"},
	{0x34, "Camcorder"},
	{0x38, "Video Monitor"},
	{0x3c, "Video Display and Loudspeaker"},
	{0x40, "Video Conferencing"},
	{0x44, "Reserved"},
	{0x48, "Gaming"},
	{0x00, NULL},
};

sub_classes_t class_peripheral1[] = {
	{0x40, "Keyboard"},
	{0x80, "Pointing device"},
	{0xC0, "Combo"},
	{0x00, NULL}
};

sub_classes_t class_peripheral2[] = {
	{0x00, "Uncategorised"},
	{0x04, "Joystick"},
	{0x08, "Gamepad"},
	{0x0c, "Remote control"},
	{0x10, "Sensing device"},
	{0x14, "Digitiser tablet"},
	{0x18, "Card Reader"},
	{0x00, NULL}
};

sub_classes_t class_imaging[] = {
	{0x10, "Display"},
	{0x20, "Camera"},
	{0x40, "Scanner"},
	{0x80, "Printer"},
	{0x00, NULL}
};

#define MAJOR_CLASS_DEFAULT 0x1f
classes_t major_device_classes[] = {
	{0x00, 0x00, 0x00, CLASS_CHOOSE, NULL, NULL, "Miscellaneous"},
	{0x01, 0xfc, 0x00, CLASS_CHOOSE, class_computer, NULL, "Computer"},
	{0x02, 0xfc, 0x00, CLASS_CHOOSE, class_phone, NULL, "Phone"},
	{0x03, 0xe0, 0x00, CLASS_CHOOSE, class_network, NULL, "Network"},
	{0x04, 0xfc, 0x00, CLASS_CHOOSE, class_av, NULL, "Audio-Video"},
	{0x05, 0xc0, 0x3c, CLASS_CHOOSE,
	  class_peripheral1, class_peripheral2, "Peripheral"},
	{0x06, 0xf0, 0x00, CLASS_BITMASK, class_imaging, NULL, "Imaging"},
	{0x1f, 0x00, 0x00, CLASS_CHOOSE, NULL, NULL, "Uncategorised"},
	{0x00, 0x00, 0x00, CLASS_CHOOSE, NULL, NULL, NULL}
};

sub_classes_t service_classes[] = {
	{0x001, "Limited Discoverable mode"},
	{0x002, "Reserved"},
	{0x004, "Reserved"},
	{0x008, "Positioning"},
	{0x010, "Networking"},
	{0x020, "Rendering"},
	{0x040, "Capturing"},
	{0x080, "Object Transfer"},
	{0x100, "Audio"},
	{0x200, "Telephony"},
	{0x400, "Information"},
	{0, NULL}
};


/* format the device classes */
int sdp_format_classes(device_t *p, char *buf, int sz, int len)
{
	uint32_t services, majorc, minorc;
	int majorc_id, found, i;
	sub_classes_t *scp;

	services = (p->class & 0x00ffe000) >> 13;
	majorc = (p->class & 0x00001f00) >> 8;
	minorc = (p->class & 0x000000fc);

	/* class stuff */
	for (majorc_id=found=i=0; major_device_classes[i].class_name; i++) {
		if (major_device_classes[i].class_id == majorc) {
			found = 1;
			majorc_id = i;
			strncpy(buf+len, major_device_classes[i].class_name, sz-len);
			len += strlen(major_device_classes[i].class_name);
			break;
		}
	}
	if (len >= sz) return len;

	if (!found) {
		for (i=0; major_device_classes[i].class_name; i++) {
			if (major_device_classes[i].class_id == MAJOR_CLASS_DEFAULT) {
				strncpy(buf+len,major_device_classes[i].class_name,sz-len);
				len += strlen(major_device_classes[i].class_name);
				break;
			}
		}
		if (len >= sz) return len;
	}

	if (!found || !major_device_classes[majorc_id].sub_major)
		goto sdp_format_classes_services;

	/* the minor */
	scp = major_device_classes[majorc_id].sub_major;
	for (found=i=0; scp[i].class_name; i++) {
		if ((minorc & major_device_classes[majorc_id].sub_major_mask)
		  == scp[i].class_id) {
			if (found)
				strncpy(buf+len, ",", sz-len);
			else
				strncpy(buf+len, "/", sz-len);
			len++;
			if (len >= sz) return len;

			strncpy(buf+len, scp[i].class_name, sz-len);
			len += strlen(scp[i].class_name);
			if (len >= sz) return len;

			found++;
			if (major_device_classes[majorc_id].type == CLASS_CHOOSE)
				break;
		}
	}

 	if (major_device_classes[majorc_id].sub_minor) {
		/* the really minor */
		scp = major_device_classes[majorc_id].sub_minor;
		for (found=i=0; scp[i].class_name; i++) {
			if ((minorc & major_device_classes[majorc_id].sub_minor_mask)
			  == scp[i].class_id) {
				if (found)
					strncpy(buf+len, ",", sz-len);
				else
					strncpy(buf+len, "/", sz-len);
				len++;
				if (len >= sz) return len;

				strncpy(buf+len, scp[i].class_name, sz-len);
				len += strlen(scp[i].class_name);
				if (len >= sz) return len;

				found++;
				if (major_device_classes[majorc_id].type == CLASS_CHOOSE)
					break;
			}
		}
        /* end the really minor */
	}


	sdp_format_classes_services:
	/* print the services */
	strncpy(buf+len, "\nServices:      ", sz-len);
	len += 16;
	if (len >= sz) return len;

	for (found=i=0; service_classes[i].class_name; i++) {
		if (0 != (service_classes[i].class_id & services)) {
			if(found) {
				strncpy(buf+len, ",", sz-len);
				len++;
				if (len >= sz) return len;
			}
			strncpy(buf+len, service_classes[i].class_name, sz-len);
			len += strlen(service_classes[i].class_name);
			if (len >= sz) return len;

			found=1;
		}
	}

	strncpy(buf+len, "\n", sz-len);
	len ++;

	return len;
}



/* language attributes */
void print_lang_attr(void *value, void *user)
{
	sdp_lang_attr_t *lang = (sdp_lang_attr_t *)value;
	cbuf_t *str = (cbuf_t*)user;
	if (str->len >= str->sz) return;

	str->len += snprintf(str->buf + str->len, str->sz,
	  "  code_ISO639: 0x%02x\n", lang->code_ISO639);
	if (str->len >= str->sz) return;
	str->len += snprintf(str->buf + str->len, str->sz,
	  "  encoding:    0x%02x\n", lang->encoding);
	if (str->len >= str->sz) return;
	str->len += snprintf(str->buf + str->len, str->sz,
	  "  base_offset: 0x%02x\n", lang->base_offset);
	if (str->len >= str->sz) return;
}   


/* service descriptions */
static void print_service_desc(void *value, void *user)
{
	char uustr[MAX_LEN_PROTOCOL_UUID_STR];
	sdp_data_t *p = (sdp_data_t *)value, *s;
	int i = 0, proto = 0;
	cbuf_t *str = (cbuf_t*)user;
	if (str->len >= str->sz) return;
   
	for (; p; p = p->next, i++) {
		switch (p->dtd) {
		case SDP_UUID16:
		case SDP_UUID32:
		case SDP_UUID128:
			sdp_uuid2strn(&p->val.uuid, UUID_str, MAX_LEN_UUID_STR);
			sdp_proto_uuid2strn(&p->val.uuid, uustr, sizeof(uustr));
			proto = sdp_uuid_to_proto(&p->val.uuid);
			str->len += snprintf(str->buf + str->len, str->sz,
			  "  \"%s\" (0x%s)\n", uustr, UUID_str);
			if (str->len >= str->sz) return;
			break;
		case SDP_UINT8:
			if (proto == RFCOMM_UUID) {
				str->len += snprintf(str->buf + str->len, str->sz,
				  "    Channel: %d\n", p->val.uint8);
			} else {
				str->len += snprintf(str->buf + str->len, str->sz,
				  "    uint8: 0x%x\n", p->val.uint8);
			}
			if (str->len >= str->sz) return;
			break;
		case SDP_UINT16:
			if (proto == L2CAP_UUID) {
				if (i == 1)
					str->len += snprintf(str->buf + str->len, str->sz,
					  "    PSM: %d\n", p->val.uint16);
				else
					str->len += snprintf(str->buf + str->len, str->sz,
					  "    Version: 0x%04x\n", p->val.uint16);
			} else if (proto == BNEP_UUID)
				if (i == 1)
					str->len += snprintf(str->buf + str->len, str->sz,
					  "    Version: 0x%04x\n", p->val.uint16);
				else
					str->len += snprintf(str->buf + str->len, str->sz,
					  "    uint16: 0x%x\n", p->val.uint16);
			else
				str->len += snprintf(str->buf + str->len, str->sz,
				  "    uint16: 0x%x\n", p->val.uint16);
			if (str->len >= str->sz) return;
			break;
		case SDP_SEQ16:
			strncpy(str->buf + str->len, "    SEQ16:", str->sz - str->len);
			str->len += 10;
			if (str->len >= str->sz) return;

			for (s = p->val.dataseq; s; s = s->next) {
				str->len += snprintf(str->buf + str->len, str->sz,
				  " %x", s->val.uint16);
				if (str->len >= str->sz) return;
			}

			strncpy(str->buf + str->len, "\n", str->sz - str->len);
			str->len++;
			if (str->len >= str->sz) return;
			break;
		case SDP_SEQ8:
			strncpy(str->buf + str->len, "    SEQ8:", str->sz - str->len);
			str->len += 9;
			if (str->len >= str->sz) return;

			for (s = p->val.dataseq; s; s = s->next) {
				str->len += snprintf(str->buf + str->len, str->sz,
				  " %x", s->val.uint8);
				if (str->len >= str->sz) return;
			}
			
			strncpy(str->buf + str->len, "\n", str->sz - str->len);
			str->len++;
			if (str->len >= str->sz) return;
			break;
		default:
			str->len += snprintf(str->buf + str->len, str->sz,
			  "    FIXME: dtd=0%x\n", p->dtd);
			if (str->len >= str->sz) return;
			break;
		}
	}
}



/* sdp service classes */
static void print_service_class(void *value, void *userData)
{
	char ServiceClassUUID_str[MAX_LEN_SERVICECLASS_UUID_STR];
	uuid_t *uuid = (uuid_t *)value;
	cbuf_t *str = (cbuf_t*)userData;

	if (str->len >= str->sz) return;

	sdp_uuid2strn(uuid, UUID_str, MAX_LEN_UUID_STR); 
	sdp_svclass_uuid2strn(uuid, ServiceClassUUID_str,
	  MAX_LEN_SERVICECLASS_UUID_STR);
	str->len += snprintf(str->buf + str->len, str->sz,
	  "  \"%s\" (0x%s)\n",
	  ServiceClassUUID_str, UUID_str);
}

/* sdp access protos */
void print_access_protos(void *value, void *userData)
{
	sdp_list_t *protDescSeq = (sdp_list_t *)value;
	sdp_list_foreach(protDescSeq, print_service_desc, userData);
}

void print_profile_desc(void *value, void *userData)
{
	cbuf_t *str = (cbuf_t*)userData;
	sdp_profile_desc_t *desc = (sdp_profile_desc_t *)value;
	char pstr[MAX_LEN_PROFILEDESCRIPTOR_UUID_STR];

	if (str->len >= str->sz) return;

	sdp_uuid2strn(&desc->uuid, UUID_str, MAX_LEN_UUID_STR);
	sdp_profile_uuid2strn(&desc->uuid, pstr,
	  MAX_LEN_PROFILEDESCRIPTOR_UUID_STR);

	str->len += snprintf(str->buf + str->len, str->sz,
	  "  \"%s\" (0x%s)\n", pstr, UUID_str);
	if (str->len >= str->sz) return;

	if (desc->version) {
		str->len += snprintf(str->buf + str->len, str->sz,
		  "    Version: 0x%04x\n", desc->version);
		if (str->len >= str->sz) return;
	}
}


/* print the record */
int sdp_sprint_service_attr(cbuf_t *str, sdp_record_t *rec)
{
	sdp_list_t *list = 0, *proto = 0;
	sdp_data_t *d;

	/* initialise the globa struct */
	if (!str) return 0;
	if (str->len >= str->sz) return 1;

	/* go go go */
	d = sdp_data_get(rec, SDP_ATTR_SVCNAME_PRIMARY);
	if (d) {
		str->len += snprintf(str->buf + str->len, str->sz,
		  "Service Name: %s\n", d->val.str);
		if (str->len >= str->sz) return 1;
	}
	d = sdp_data_get(rec, SDP_ATTR_SVCDESC_PRIMARY);
	if (d) {
		str->len += snprintf(str->buf + str->len, str->sz,
		  "Service Description: %s\n", d->val.str);
		if (str->len >= str->sz) return 1;
	}
	d = sdp_data_get(rec, SDP_ATTR_PROVNAME_PRIMARY);
	if (d) {
		str->len += snprintf(str->buf + str->len, str->sz,
		  "Service Provider: %s\n", d->val.str);
		if (str->len >= str->sz) return 1;
	}

	str->len += snprintf(str->buf + str->len, str->sz,
	  "Service RecHandle: 0x%x\n", rec->handle);
	if (str->len >= str->sz) return 1;

	/* this is from sdptool.c */
	if (sdp_get_service_classes(rec, &list) == 0) {
		str->len += snprintf(str->buf + str->len, str->sz,
		  "Service Class ID List:\n");
		if (str->len >= str->sz) return 1;
		sdp_list_foreach(list, print_service_class, str);
		sdp_list_free(list, free);
		if (str->len >= str->sz) return 1;
	}
	if (sdp_get_access_protos(rec, &proto) == 0) {
		str->len += snprintf(str->buf + str->len, str->sz,
		  "Protocol Descriptor List:\n");
		if (str->len >= str->sz) return 1;
		sdp_list_foreach(proto, print_access_protos, str);
		sdp_list_free(proto, (sdp_free_func_t)sdp_data_free);
		if (str->len >= str->sz) return 1;
	}
	if (sdp_get_lang_attr(rec, &list) == 0) {
		str->len += snprintf(str->buf + str->len, str->sz,
		  "Language Base Attr List:\n");
		if (str->len >= str->sz) return 1;
		sdp_list_foreach(list, print_lang_attr, str);
		sdp_list_free(list, free);
		if (str->len >= str->sz) return 1;
	}
	if (sdp_get_profile_descs(rec, &list) == 0) {
		str->len += snprintf(str->buf + str->len, str->sz,
		  "Profile Descriptor List:\n");
		if (str->len >= str->sz) return 1;
		sdp_list_foreach(list, print_profile_desc, str);
		sdp_list_free(list, free);
		if (str->len >= str->sz) return 1;
	}

	strncpy(str->buf + str->len, "\n", str->sz - str->len);
	str->len++;
	if (str->len >= str->sz) return 1;

	return 0;
}

