/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2004-2011  Marcel Holtmann <marcel@holtmann.org>
 *
 *
 *  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 <ctype.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#include "parser.h"

#define CAPI_U8(frm)  (get_u8(frm))
#define CAPI_U16(frm) (btohs(htons(get_u16(frm))))
#define CAPI_U32(frm) (btohl(htonl(get_u32(frm))))

static char *cmd2str(uint8_t cmd)
{
	switch (cmd) {
	case 0x01:
		return "ALERT";
	case 0x02:
		return "CONNECT";
	case 0x03:
		return "CONNECT_ACTIVE";
	case 0x04:
		return "DISCONNECT";
	case 0x05:
		return "LISTEN";
	case 0x08:
		return "INFO";
	case 0x20:
		return "INTEROPERABILITY";
	case 0x41:
		return "SELECT_B_PROTOCOL";
	case 0x80:
		return "FACILITY";
	case 0x82:
		return "CONNECT_B3";
	case 0x83:
		return "CONNECT_B3_ACTIVE";
	case 0x84:
		return "DISCONNECT_B3";
	case 0x86:
		return "DATA_B3";
	case 0x87:
		return "RESET_B3";
	case 0x88:
		return "CONNECT_B3_T90_ACTIVE";
	case 0xff:
		return "MANUFACTURER";
	default:
		return "UNKNOWN";
	}
}

static char *subcmd2str(uint8_t subcmd)
{
	switch (subcmd) {
	case 0x80:
		return "REQ";
	case 0x81:
		return "CONF";
	case 0x82:
		return "IND";
	case 0x83:
		return "RESP";
	default:
		return "UNKN";
	}
}

static char *interopsel2str(uint16_t sel)
{
	switch (sel) {
	case 0x0000:
		return "USB Device Management";
	case 0x0001:
		return "Bluetooth Device Management";
	default:
		return "Unknown";
	}
}

static char *func2str(uint16_t func)
{
	switch (func) {
	case 0:
		return "Register";
	case 1:
		return "Release";
	case 2:
		return "Get_Profile";
	case 3:
		return "Get_Manufacturer";
	case 4:
		return "Get_Version";
	case 5:
		return "Get_Serial_Number";
	case 6:
		return "Manufacturer";
	case 7:
		return "Echo_Loopback";
	default:
		return "Unknown";
	}
}

static char *facilitysel2str(uint16_t sel)
{
	switch (sel) {
	case 0x0000:
		return "Handset";
	case 0x0001:
		return "DTMF";
	case 0x0002:
		return "V.42 bis";
	case 0x0003:
		return "Supplementary Services";
	case 0x0004:
		return "Power management wakeup";
	case 0x0005:
		return "Line Interconnect";
	case 0x0006:
		return "DTMF";
	default:
		return "Unknown";
	}
}

static char *info2str(uint16_t info)
{
	switch (info) {
	case 0x0000:
		return "No error";
	case 0x0001:
		return "NCPI not supported by current protocol, NCPI ignored";
	case 0x0002:
		return "Flags not supported by current protocol, flags ignored";
	case 0x2001:
		return "Message not supported in current state";
	case 0x2002:
		return "Incorrect Controller/PLCI/NCCI";
	case 0x2003:
		return "No PLCI available";
	case 0x2004:
		return "No NCCI available";
	case 0x2005:
		return "No Listen resources available";
	case 0x2007:
		return "Illegal message parameter coding";
	case 0x2008:
		return "No interconnection resources available";
	case 0x3001:
		return "B1 protocol not supported";
	case 0x3002:
		return "B2 protocol not supported";
	case 0x3003:
		return "B3 protocol not supported";
	case 0x3004:
		return "B1 protocol parameter not supported";
	case 0x3005:
		return "B2 protocol parameter not supported";
	case 0x3006:
		return "B3 protocol parameter not supported";
	case 0x3007:
		return "B protocol combination not supported";
	case 0x3008:
		return "NCPI not supported";
	case 0x3009:
		return "CIP Value unknown";
	case 0x300A:
		return "Flags not supported (reserved bits)";
	case 0x300B:
		return "Facility not supported";
	case 0x300C:
		return "Data length not supported by current protocol";
	case 0x300D:
		return "Reset procedure not supported by current protocol";
	case 0x300F:
		return "Unsupported interoperability";
	case 0x3011:
		return "Facility specific function not supported";
	case 0x3301:
		return "Protocol error, Layer 1";
	case 0x3302:
		return "Protocol error, Layer 2";
	case 0x3303:
		return "Protocol error, Layer 3";
	case 0x3304:
		return "Another application got that call";
	case 0x3305:
		return "Cleared by Call Control Supervision";
	case 0x3400:
		/* The cause value received from the network in a cause
		 * information element (Octet 4) is indicated in the field 00 */
		return "Disconnect cause from the network in accordance with Q.850/ETS 300 102-1";
	default:
		return "Unknown";
	}
}

static void profile(int level, struct frame *frm)
{
	uint16_t nctr, nchn;
	uint32_t value;

	nctr = CAPI_U16(frm);
	nchn = CAPI_U16(frm);

	if (nchn > 0) {
		p_indent(level, frm);
		printf("Controller: %d\n", nctr);
		p_indent(level, frm);
		printf("Number of B-channels: %d\n", nchn);

		value = CAPI_U32(frm);
		p_indent(level, frm);
		printf("Global options: 0x%04x\n", value);
		value = CAPI_U32(frm);
		p_indent(level, frm);
		printf("B1 protocol support: 0x%08x\n", value);
		value = CAPI_U32(frm);
		p_indent(level, frm);
		printf("B2 protocol support: 0x%08x\n", value);
		value = CAPI_U32(frm);
		p_indent(level, frm);
		printf("B3 protocol support: 0x%08x\n", value);

		frm->ptr += 24;
		frm->len -= 24;

		p_indent(level, frm);
		printf("Manufacturer-specific information:\n");
		hex_dump(level, frm, 20);
	} else {
		p_indent(level, frm);
		printf("Number of controllers: %d\n", nctr);
	}
}

static void cmd_common(int level, uint8_t subcmd, struct frame *frm)
{
	uint32_t val;
	uint16_t info, ncci;
	uint8_t ctr, plci;

	val = CAPI_U32(frm);
	ctr = val & 0xff;
	plci = (val & 0xff00) >> 8;
	ncci = (val & 0xffff0000) >> 16;

	p_indent(level, frm);
	printf("Controller: %d %s\n", ctr & 0x7f, ctr & 0x80 ? "Ext." : "Int.");

	if (plci > 0) {
		p_indent(level, frm);
		printf("PLCI: 0x%02x\n", plci);
	}

	if (ncci > 0) {
		p_indent(level, frm);
		printf("NCCI: 0x%04x\n", ncci);
	}

	if (subcmd == 0x81) {
		info = CAPI_U16(frm);
		p_indent(level, frm);
		printf("Info: 0x%04x (%s)\n", info, info2str(info));
	}
}

static void cmd_alert(int level, uint8_t subcmd, struct frame *frm)
{
	uint8_t len;

	cmd_common(level, subcmd, frm);

	if (subcmd == 0x80) {
		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("Additional info:\n");
			hex_dump(level, frm, len);
		}
	}
}

static void cmd_connect(int level, uint8_t subcmd, struct frame *frm)
{
	uint16_t cip;
	uint8_t len;

	cmd_common(level, subcmd, frm);

	if (subcmd == 0x81)
		return;

	cip = CAPI_U16(frm);
	p_indent(level, frm);
	printf("CIP value: 0x%04x\n", cip);

	len = CAPI_U8(frm);
	frm->ptr += len;
	frm->len -= len;
	len = CAPI_U8(frm);
	frm->ptr += len;
	frm->len -= len;
	len = CAPI_U8(frm);
	frm->ptr += len;
	frm->len -= len;
	len = CAPI_U8(frm);
	frm->ptr += len;
	frm->len -= len;

	raw_dump(level, frm);
}

static void cmd_disconnect(int level, uint8_t subcmd, struct frame *frm)
{
	uint16_t reason;
	uint8_t len;

	cmd_common(level, subcmd, frm);

	if (subcmd == 0x80) {
		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("Additional info:\n");
			hex_dump(level, frm, len);
		}
	}

	if (subcmd == 0x82) {
		reason = CAPI_U16(frm);
		p_indent(level, frm);
		printf("Reason: 0x%04x (%s)\n", reason, info2str(reason));
	}
}

static void cmd_connect_active(int level, uint8_t subcmd, struct frame *frm)
{
	uint8_t len;

	cmd_common(level, subcmd, frm);

	if (subcmd == 0x82) {
		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("Connected number:\n");
			hex_dump(level, frm, len);
		}

		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("Connected subaddress:\n");
			hex_dump(level, frm, len);
		}

		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("LLC:\n");
			hex_dump(level, frm, len);
		}
	}
}

static void cmd_listen(int level, uint8_t subcmd, struct frame *frm)
{
	uint32_t mask;
	uint8_t len;

	cmd_common(level, subcmd, frm);

	if (subcmd == 0x80) {
		mask = CAPI_U32(frm);
		p_indent(level, frm);
		printf("Info mask: 0x%08x\n", mask);

		mask = CAPI_U32(frm);
		p_indent(level, frm);
		printf("CIP mask:  0x%08x", mask);

		mask = CAPI_U32(frm);
		if (mask > 0)
			printf(" 0x%08x\n", mask);
		else
			printf("\n");

		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("Calling party number:\n");
			hex_dump(level, frm, len);
		}
		frm->ptr += len;
		frm->len -= len;

		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("Calling party subaddress:\n");
			hex_dump(level, frm, len);
		}
		frm->ptr += len;
		frm->len -= len;
	}
}

static void cmd_info(int level, uint8_t subcmd, struct frame *frm)
{
	uint8_t len;
	uint16_t info;

	cmd_common(level, subcmd, frm);

	switch (subcmd) {
	case 0x80:
		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("Called party number:\n");
			hex_dump(level, frm, len);
		}
		frm->ptr += len;
		frm->len -= len;

		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("Additional info:\n");
			hex_dump(level, frm, len);
		}
		break;

	case 0x82:
		info = CAPI_U16(frm);
		p_indent(level, frm);
		printf("Info number: %d\n", info);

		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("Info element:\n");
			hex_dump(level, frm, len);
		}
		break;
	}
}

static void cmd_interoperability(int level, uint8_t subcmd, struct frame *frm)
{
	uint16_t sel, func, info;
	uint16_t nconn, datablkcnt, datablklen;
	uint32_t ctr, value, major, minor;

	info = (subcmd == 0x81) ? CAPI_U16(frm) : 0;
	sel = CAPI_U16(frm);
	CAPI_U8(frm);
	if (subcmd != 0x83) {
		func = CAPI_U16(frm);
		CAPI_U8(frm);
	} else
		func = 0;

	p_indent(level, frm);
	printf("Selector: 0x%04x (%s)\n", sel, interopsel2str(sel));

	switch (sel) {
	case 0x0001:
		p_indent(level, frm);
		printf("Function: %d (%s)\n", func, func2str(func));

		switch (subcmd) {
		case 0x80:
			switch (func) {
			case 0:
				nconn = CAPI_U16(frm);
				p_indent(level + 1, frm);
				printf("maxLogicalConnections: %d\n", nconn);
				datablkcnt = CAPI_U16(frm);
				p_indent(level + 1, frm);
				printf("maxBDataBlocks: %d\n", datablkcnt);
				datablklen = CAPI_U16(frm);
				p_indent(level + 1, frm);
				printf("maxBDataLen: %d\n", datablklen);
				break;
			case 2:
			case 3:
			case 4:
			case 5:
				ctr = CAPI_U32(frm);
				p_indent(level + 1, frm);
				printf("Controller: %d\n", ctr);
				break;
			default:
				raw_dump(level + 1, frm);
				break;
			}
			break;

		case 0x81:
			switch (func) {
			case 0:
			case 1:
				info = CAPI_U16(frm);
				p_indent(level + 1, frm);
				printf("Info: 0x%04x (%s)\n", info, info2str(info));
				break;
			case 2:
				info = CAPI_U16(frm);
				p_indent(level + 1, frm);
				printf("Info: 0x%04x (%s)\n", info, info2str(info));
				CAPI_U8(frm);
				profile(level + 1, frm);
				break;
			case 3:
				info = CAPI_U16(frm);
				p_indent(level + 1, frm);
				printf("Info: 0x%04x (%s)\n", info, info2str(info));
				ctr = CAPI_U32(frm);
				p_indent(level + 1, frm);
				printf("Controller: %d\n", ctr);
				CAPI_U8(frm);
				p_indent(level + 1, frm);
				printf("Identification: \"%s\"\n", (char *) frm->ptr);
				break;
			case 4:
				value = CAPI_U32(frm);
				p_indent(level + 1, frm);
				printf("Return value: 0x%04x\n", value);
				ctr = CAPI_U32(frm);
				p_indent(level + 1, frm);
				printf("Controller: %d\n", ctr);
				p_indent(level + 1, frm);
				major = CAPI_U32(frm);
				minor = CAPI_U32(frm);
				printf("CAPI: %d.%d\n", major, minor);
				major = CAPI_U32(frm);
				minor = CAPI_U32(frm);
				p_indent(level + 1, frm);
				printf("Manufacture: %u.%01x%01x-%02u (%d.%d)\n",
					(major & 0xf0) >> 4, (major & 0x0f) << 4,
					(minor & 0xf0) >> 4, minor & 0x0f,
					major, minor);
				break;
			case 5:
				value = CAPI_U32(frm);
				p_indent(level + 1, frm);
				printf("Return value: 0x%04x\n", value);
				ctr = CAPI_U32(frm);
				p_indent(level + 1, frm);
				printf("Controller: %d\n", ctr);
				CAPI_U8(frm);
				p_indent(level + 1, frm);
				printf("Serial number: %.7s\n", (char *) frm->ptr);
				break;
			default:
				raw_dump(level + 1, frm);
				break;
			}
			break;

		default:
			raw_dump(level, frm);
			break;
		}
		break;

	default:
		p_indent(level, frm);
		printf("Function: %d\n", func);
		if (subcmd == 0x81) {
			p_indent(level, frm);
			printf("Info: 0x%04x (%s)\n", info, info2str(info));
		}
		raw_dump(level + 1, frm);
		break;
	}
}

static void cmd_facility(int level, uint8_t subcmd, struct frame *frm)
{
	uint16_t sel;

	cmd_common(level, subcmd, frm);

	sel = CAPI_U16(frm);
	CAPI_U8(frm);

	p_indent(level, frm);
	printf("Selector: 0x%04x (%s)\n", sel, facilitysel2str(sel));

	raw_dump(level, frm);
}

static void cmd_connect_b3(int level, uint8_t subcmd, struct frame *frm)
{
	uint16_t reject;
	uint8_t len;

	cmd_common(level, subcmd, frm);

	if (subcmd == 0x81)
		return;

	if (subcmd == 0x83) {
		reject = CAPI_U16(frm);
		p_indent(level, frm);
		printf("Reject: 0x%04x (%s)\n", reject, info2str(reject));
	}

	len = CAPI_U8(frm);
	if (len > 0) {
		p_indent(level, frm);
		printf("NCPI:\n");
		hex_dump(level, frm, len);
	}
}

static void cmd_connect_b3_active(int level, uint8_t subcmd, struct frame *frm)
{
	uint8_t len;

	cmd_common(level, subcmd, frm);

	if (subcmd == 0x82) {
		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("NCPI:\n");
			hex_dump(level, frm, len);
		}
	}
}

static void cmd_disconnect_b3(int level, uint8_t subcmd, struct frame *frm)
{
	uint16_t reason;
	uint8_t len;

	cmd_common(level, subcmd, frm);

	if (subcmd == 0x82) {
		reason = CAPI_U16(frm);
		p_indent(level, frm);
		printf("Reason: 0x%04x (%s)\n", reason, info2str(reason));
	}

	if (subcmd == 0x80 || subcmd == 0x82) {
		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("NCPI:\n");
			hex_dump(level, frm, len);
		}
	}
}

static void cmd_data_b3(int level, uint8_t subcmd, struct frame *frm)
{
	uint32_t data;
	uint16_t length, handle, flags, info;

	cmd_common(level, 0x00, frm);

	if (subcmd == 0x81 || subcmd == 0x83) {
		handle = CAPI_U16(frm);
		p_indent(level, frm);
		printf("Data handle: 0x%04x\n", handle);

		if (subcmd == 0x81) {
			info = CAPI_U16(frm);
			p_indent(level, frm);
			printf("Info: 0x%04x (%s)\n", info, info2str(info));
		}
	} else {
		data = CAPI_U32(frm);

		length = CAPI_U16(frm);
		p_indent(level, frm);
		printf("Data length: 0x%04x (%d bytes)\n", length, length);

		handle = CAPI_U16(frm);
		p_indent(level, frm);
		printf("Data handle: 0x%04x\n", handle);

		flags = CAPI_U16(frm);
		p_indent(level, frm);
		printf("Flags: 0x%04x\n", flags);

		if (data == 0)
			(void) get_u64(frm);

		raw_dump(level, frm);
	}
}

static void cmd_reset_b3(int level, uint8_t subcmd, struct frame *frm)
{
	uint8_t len;

	cmd_common(level, subcmd, frm);

	if (subcmd == 0x80 || subcmd == 0x82) {
		len = CAPI_U8(frm);
		if (len > 0) {
			p_indent(level, frm);
			printf("NCPI:\n");
			hex_dump(level, frm, len);
		}
	}
}

static void cmd_manufacturer(int level, uint8_t subcmd, struct frame *frm)
{
	uint32_t ctr, class, func;
	uint16_t len;
	unsigned char *id;

	ctr = CAPI_U32(frm);
	p_indent(level, frm);
	printf("Controller: %d\n", ctr);

	id = (unsigned char *) frm->ptr;
	p_indent(level, frm);
	if (isprint(id[0]) && isprint(id[1]) && isprint(id[2]) && isprint(id[3]))
		printf("Manufacturer: %.4s", id);
	else
		printf("Manufacturer: 0x%02x 0x%02x 0x%02x 0x%02x",
						id[0], id[1], id[2], id[3]);
	frm->ptr += 4;
	frm->len -= 4;

	if (!strncmp((char *) id, "AVM!", 4)) {
		class = CAPI_U32(frm);
		func = CAPI_U32(frm);
		len = CAPI_U8(frm);
		if (len == 0xff)
			len = CAPI_U16(frm);

		printf(" [class %d func %d len %d]\n", class, func, len);
	} else
		printf("\n");

	raw_dump(level, frm);
}

void capi_dump(int level, struct frame *frm)
{
	uint16_t len, appl, msgnum;
	uint8_t cmd, subcmd;

	len = CAPI_U16(frm) - 8;
	appl = CAPI_U16(frm);
	cmd = CAPI_U8(frm);
	subcmd = CAPI_U8(frm);
	msgnum = CAPI_U16(frm);

	p_indent(level, frm);

	printf("CAPI_%s_%s: appl %d msgnum %d len %d\n",
			cmd2str(cmd), subcmd2str(subcmd), appl, msgnum, len);

	switch (cmd) {
	case 0x01:
		cmd_alert(level + 1, subcmd, frm);
		break;
	case 0x02:
		cmd_connect(level + 1, subcmd, frm);
		break;
	case 0x03:
		cmd_connect_active(level + 1, subcmd, frm);
		break;
	case 0x04:
		cmd_disconnect(level + 1, subcmd, frm);
		break;
	case 0x05:
		cmd_listen(level + 1, subcmd, frm);
		break;
	case 0x08:
		cmd_info(level + 1, subcmd, frm);
		break;
	case 0x20:
		cmd_interoperability(level + 1, subcmd, frm);
		break;
	case 0x80:
		cmd_facility(level + 1, subcmd, frm);
		break;
	case 0x82:
		cmd_connect_b3(level + 1, subcmd, frm);
		break;
	case 0x83:
	case 0x88:
		cmd_connect_b3_active(level + 1, subcmd, frm);
		break;
	case 0x84:
		cmd_disconnect_b3(level + 1, subcmd, frm);
		break;
	case 0x86:
		cmd_data_b3(level + 1, subcmd, frm);
		break;
	case 0x87:
		cmd_reset_b3(level + 1, subcmd, frm);
		break;
	case 0xff:
		cmd_manufacturer(level + 1, subcmd, frm);
		break;
	default:
		raw_dump(level, frm);
		frm->ptr += len;
		frm->len -= len;
		break;
	}
}
