/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2015 Andrzej Kaczmarek * * * 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 #endif #include #include #include #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); } }