/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2015  Andrzej Kaczmarek <andrzej.kaczmarek@codecoup.pl>
 *
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; 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 <stdlib.h>
#include <string.h>

#include "lib/bluetooth.h"

#include "src/shared/util.h"
#include "bt.h"
#include "packet.h"
#include "display.h"
#include "l2cap.h"
#include "avdtp.h"
#include "a2dp.h"

/* Message Types */
#define AVDTP_MSG_TYPE_COMMAND		0x00
#define AVDTP_MSG_TYPE_GENERAL_REJECT	0x01
#define AVDTP_MSG_TYPE_RESPONSE_ACCEPT	0x02
#define AVDTP_MSG_TYPE_RESPONSE_REJECT	0x03

/* Signal Identifiers */
#define AVDTP_DISCOVER			0x01
#define AVDTP_GET_CAPABILITIES		0x02
#define AVDTP_SET_CONFIGURATION		0x03
#define AVDTP_GET_CONFIGURATION		0x04
#define AVDTP_RECONFIGURE		0x05
#define AVDTP_OPEN			0x06
#define AVDTP_START			0x07
#define AVDTP_CLOSE			0x08
#define AVDTP_SUSPEND			0x09
#define AVDTP_ABORT			0x0a
#define AVDTP_SECURITY_CONTROL		0x0b
#define AVDTP_GET_ALL_CAPABILITIES	0x0c
#define AVDTP_DELAYREPORT		0x0d

/* Service Categories */
#define AVDTP_MEDIA_TRANSPORT		0x01
#define AVDTP_REPORTING			0x02
#define AVDTP_RECOVERY			0x03
#define AVDTP_CONTENT_PROTECTION	0x04
#define AVDTP_HEADER_COMPRESSION	0x05
#define AVDTP_MULTIPLEXING		0x06
#define AVDTP_MEDIA_CODEC		0x07
#define AVDTP_DELAY_REPORTING		0x08

struct avdtp_frame {
	uint8_t hdr;
	uint8_t sig_id;
	struct l2cap_frame l2cap_frame;
};

static inline bool is_configuration_sig_id(uint8_t sig_id)
{
	return (sig_id == AVDTP_SET_CONFIGURATION) ||
			(sig_id == AVDTP_GET_CONFIGURATION) ||
			(sig_id == AVDTP_RECONFIGURE);
}

static const char *msgtype2str(uint8_t msgtype)
{
	switch (msgtype) {
	case 0:
		return "Command";
	case 1:
		return "General Reject";
	case 2:
		return "Response Accept";
	case 3:
		return "Response Reject";
	}

	return "";
}

static const char *sigid2str(uint8_t sigid)
{
	switch (sigid) {
	case AVDTP_DISCOVER:
		return "Discover";
	case AVDTP_GET_CAPABILITIES:
		return "Get Capabilities";
	case AVDTP_SET_CONFIGURATION:
		return "Set Configuration";
	case AVDTP_GET_CONFIGURATION:
		return "Get Configuration";
	case AVDTP_RECONFIGURE:
		return "Reconfigure";
	case AVDTP_OPEN:
		return "Open";
	case AVDTP_START:
		return "Start";
	case AVDTP_CLOSE:
		return "Close";
	case AVDTP_SUSPEND:
		return "Suspend";
	case AVDTP_ABORT:
		return "Abort";
	case AVDTP_SECURITY_CONTROL:
		return "Security Control";
	case AVDTP_GET_ALL_CAPABILITIES:
		return "Get All Capabilities";
	case AVDTP_DELAYREPORT:
		return "Delay Report";
	default:
		return "Reserved";
	}
}

static const char *error2str(uint8_t error)
{
	switch (error) {
	case 0x01:
		return "BAD_HEADER_FORMAT";
	case 0x11:
		return "BAD_LENGTH";
	case 0x12:
		return "BAD_ACP_SEID";
	case 0x13:
		return "SEP_IN_USE";
	case 0x14:
		return "SEP_NOT_IN_USER";
	case 0x17:
		return "BAD_SERV_CATEGORY";
	case 0x18:
		return "BAD_PAYLOAD_FORMAT";
	case 0x19:
		return "NOT_SUPPORTED_COMMAND";
	case 0x1a:
		return "INVALID_CAPABILITIES";
	case 0x22:
		return "BAD_RECOVERY_TYPE";
	case 0x23:
		return "BAD_MEDIA_TRANSPORT_FORMAT";
	case 0x25:
		return "BAD_RECOVERY_FORMAT";
	case 0x26:
		return "BAD_ROHC_FORMAT";
	case 0x27:
		return "BAD_CP_FORMAT";
	case 0x28:
		return "BAD_MULTIPLEXING_FORMAT";
	case 0x29:
		return "UNSUPPORTED_CONFIGURATION";
	case 0x31:
		return "BAD_STATE";
	default:
		return "Unknown";
	}
}

static const char *mediatype2str(uint8_t media_type)
{
	switch (media_type) {
	case 0x00:
		return "Audio";
	case 0x01:
		return "Video";
	case 0x02:
		return "Multimedia";
	default:
		return "Reserved";
	}
}

static const char *mediacodec2str(uint8_t codec)
{
	switch (codec) {
	case 0x00:
		return "SBC";
	case 0x01:
		return "MPEG-1,2 Audio";
	case 0x02:
		return "MPEG-2,4 AAC";
	case 0x04:
		return "ATRAC Family";
	case 0xff:
		return "Non-A2DP";
	default:
		return "Reserved";
	}
}

static const char *cptype2str(uint8_t cp)
{
	switch (cp) {
	case 0x0001:
		return "DTCP";
	case 0x0002:
		return "SCMS-T";
	default:
		return "Reserved";
	}
}

static const char *servicecat2str(uint8_t service_cat)
{
	switch (service_cat) {
	case AVDTP_MEDIA_TRANSPORT:
		return "Media Transport";
	case AVDTP_REPORTING:
		return "Reporting";
	case AVDTP_RECOVERY:
		return "Recovery";
	case AVDTP_CONTENT_PROTECTION:
		return "Content Protection";
	case AVDTP_HEADER_COMPRESSION:
		return "Header Compression";
	case AVDTP_MULTIPLEXING:
		return "Multiplexing";
	case AVDTP_MEDIA_CODEC:
		return "Media Codec";
	case AVDTP_DELAY_REPORTING:
		return "Delay Reporting";
	default:
		return "Reserved";
	}
}

static bool avdtp_reject_common(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t error;

	if (!l2cap_frame_get_u8(frame, &error))
		return false;

	print_field("Error code: %s (0x%02x)", error2str(error), error);

	return true;
}

static bool service_content_protection(struct avdtp_frame *avdtp_frame,
								uint8_t losc)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint16_t type = 0;

	if (losc < 2)
		return false;

	if (!l2cap_frame_get_le16(frame, &type))
		return false;

	losc -= 2;

	print_field("%*cContent Protection Type: %s (0x%04x)", 2, ' ',
							cptype2str(type), type);

	/* TODO: decode protection specific information */
	packet_hexdump(frame->data, losc);

	l2cap_frame_pull(frame, frame, losc);

	return true;
}

static bool service_media_codec(struct avdtp_frame *avdtp_frame, uint8_t losc)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = 0;
	uint8_t codec = 0;

	if (losc < 2)
		return false;

	l2cap_frame_get_u8(frame, &type);
	l2cap_frame_get_u8(frame, &codec);

	losc -= 2;

	print_field("%*cMedia Type: %s (0x%02x)", 2, ' ',
					mediatype2str(type >> 4), type >> 4);

	print_field("%*cMedia Codec: %s (0x%02x)", 2, ' ',
					mediacodec2str(codec), codec);

	if (is_configuration_sig_id(avdtp_frame->sig_id))
		return a2dp_codec_cfg(codec, losc, frame);
	else
		return a2dp_codec_cap(codec, losc, frame);
}

static bool decode_capabilities(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t service_cat;
	uint8_t losc;

	while (l2cap_frame_get_u8(frame, &service_cat)) {
		print_field("Service Category: %s (0x%02x)",
				servicecat2str(service_cat), service_cat);

		if (!l2cap_frame_get_u8(frame, &losc))
			return false;

		if (frame->size < losc)
			return false;

		switch (service_cat) {
		case AVDTP_CONTENT_PROTECTION:
			if (!service_content_protection(avdtp_frame, losc))
				return false;
			break;
		case AVDTP_MEDIA_CODEC:
			if (!service_media_codec(avdtp_frame, losc))
				return false;
			break;
		case AVDTP_MEDIA_TRANSPORT:
		case AVDTP_REPORTING:
		case AVDTP_RECOVERY:
		case AVDTP_HEADER_COMPRESSION:
		case AVDTP_MULTIPLEXING:
		case AVDTP_DELAY_REPORTING:
		default:
			packet_hexdump(frame->data, losc);
			l2cap_frame_pull(frame, frame, losc);
		}

	}

	return true;
}

static bool avdtp_discover(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;
	uint8_t info;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		while (l2cap_frame_get_u8(frame, &seid)) {
			print_field("ACP SEID: %d", seid >> 2);

			if (!l2cap_frame_get_u8(frame, &info))
				return false;

			print_field("%*cMedia Type: %s (0x%02x)", 2, ' ',
					mediatype2str(info >> 4), info >> 4);
			print_field("%*cSEP Type: %s (0x%02x)", 2, ' ',
						info & 0x04 ? "SNK" : "SRC",
						(info >> 3) & 0x01);
			print_field("%*cIn use: %s", 2, ' ',
						seid & 0x02 ? "Yes" : "No");
		}
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_get_capabilities(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		return true;
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		return decode_capabilities(avdtp_frame);
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_set_configuration(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t acp_seid, int_seid;
	uint8_t service_cat;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &acp_seid))
			return false;

		print_field("ACP SEID: %d", acp_seid >> 2);

		if (!l2cap_frame_get_u8(frame, &int_seid))
			return false;

		print_field("INT SEID: %d", int_seid >> 2);

		return decode_capabilities(avdtp_frame);
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		if (!l2cap_frame_get_u8(frame, &service_cat))
			return false;

		print_field("Service Category: %s (0x%02x)",
				servicecat2str(service_cat), service_cat);

		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_get_configuration(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		return true;
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		return decode_capabilities(avdtp_frame);
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_reconfigure(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;
	uint8_t service_cat;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		return decode_capabilities(avdtp_frame);
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		if (!l2cap_frame_get_u8(frame, &service_cat))
			return false;

		print_field("Service Category: %s (0x%02x)",
				servicecat2str(service_cat), service_cat);

		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_open(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		return true;
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_start(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		while (l2cap_frame_get_u8(frame, &seid))
			print_field("ACP SEID: %d", seid >> 2);

		return true;
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_close(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		return true;
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_suspend(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		while (l2cap_frame_get_u8(frame, &seid))
			print_field("ACP SEID: %d", seid >> 2);

		return true;
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_abort(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		return true;
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		return true;
	}

	return false;
}

static bool avdtp_security_control(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		/* TODO: decode more information */
		packet_hexdump(frame->data, frame->size);
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		/* TODO: decode more information */
		packet_hexdump(frame->data, frame->size);
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_delayreport(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	uint8_t type = avdtp_frame->hdr & 0x03;
	uint8_t seid;
	uint16_t delay;

	switch (type) {
	case AVDTP_MSG_TYPE_COMMAND:
		if (!l2cap_frame_get_u8(frame, &seid))
			return false;

		print_field("ACP SEID: %d", seid >> 2);

		if (!l2cap_frame_get_be16(frame, &delay))
			return false;

		print_field("Delay: %d.%dms", delay / 10, delay % 10);

		return true;
	case AVDTP_MSG_TYPE_RESPONSE_ACCEPT:
		return true;
	case AVDTP_MSG_TYPE_RESPONSE_REJECT:
		return avdtp_reject_common(avdtp_frame);
	}

	return false;
}

static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
	struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
	const char *pdu_color;
	uint8_t hdr;
	uint8_t sig_id;
	uint8_t nosp = 0;

	if (frame->in)
		pdu_color = COLOR_MAGENTA;
	else
		pdu_color = COLOR_BLUE;

	if (!l2cap_frame_get_u8(frame, &hdr))
		return false;

	avdtp_frame->hdr = hdr;

	/* Continue Packet || End Packet */
	if (((hdr & 0x0c) == 0x08) || ((hdr & 0x0c) == 0x0c)) {
		/* TODO: handle fragmentation */
		packet_hexdump(frame->data, frame->size);
		return true;
	}

	/* Start Packet */
	if ((hdr & 0x0c) == 0x04) {
		if (!l2cap_frame_get_u8(frame, &nosp))
			return false;
	}

	if (!l2cap_frame_get_u8(frame, &sig_id))
		return false;

	sig_id &= 0x3f;

	avdtp_frame->sig_id = sig_id;

	print_indent(6, pdu_color, "AVDTP: ", sigid2str(sig_id), COLOR_OFF,
			" (0x%02x) %s (0x%02x) type 0x%02x label %d nosp %d",
			sig_id, msgtype2str(hdr & 0x03), hdr & 0x03,
			hdr & 0x0c, hdr >> 4, nosp);

	/* Start Packet */
	if ((hdr & 0x0c) == 0x04) {
		/* TODO: handle fragmentation */
		packet_hexdump(frame->data, frame->size);
		return true;
	}

	/* General Reject */
	if ((hdr & 0x03) == 0x03)
		return true;

	switch (sig_id) {
	case AVDTP_DISCOVER:
		return avdtp_discover(avdtp_frame);
	case AVDTP_GET_CAPABILITIES:
	case AVDTP_GET_ALL_CAPABILITIES:
		return avdtp_get_capabilities(avdtp_frame);
	case AVDTP_SET_CONFIGURATION:
		return avdtp_set_configuration(avdtp_frame);
	case AVDTP_GET_CONFIGURATION:
		return avdtp_get_configuration(avdtp_frame);
	case AVDTP_RECONFIGURE:
		return avdtp_reconfigure(avdtp_frame);
	case AVDTP_OPEN:
		return avdtp_open(avdtp_frame);
	case AVDTP_START:
		return avdtp_start(avdtp_frame);
	case AVDTP_CLOSE:
		return avdtp_close(avdtp_frame);
	case AVDTP_SUSPEND:
		return avdtp_suspend(avdtp_frame);
	case AVDTP_ABORT:
		return avdtp_abort(avdtp_frame);
	case AVDTP_SECURITY_CONTROL:
		return avdtp_security_control(avdtp_frame);
	case AVDTP_DELAYREPORT:
		return avdtp_delayreport(avdtp_frame);
	}

	packet_hexdump(frame->data, frame->size);

	return true;
}

void avdtp_packet(const struct l2cap_frame *frame)
{
	struct avdtp_frame avdtp_frame;
	bool ret;

	l2cap_frame_pull(&avdtp_frame.l2cap_frame, frame, 0);

	switch (frame->seq_num) {
	case 1:
		ret = avdtp_signalling_packet(&avdtp_frame);
		break;
	default:
		if (packet_has_filter(PACKET_FILTER_SHOW_A2DP_STREAM))
			packet_hexdump(frame->data, frame->size);
		return;
	}

	if (!ret) {
		print_text(COLOR_ERROR, "PDU malformed");
		packet_hexdump(frame->data, frame->size);
	}
}
