|  | /* | 
|  | * | 
|  | *  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 & 0x08 ? "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); | 
|  | } | 
|  | } |