| /* |
| * |
| * 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: |
| packet_hexdump(frame->data, frame->size); |
| return; |
| } |
| |
| if (!ret) { |
| print_text(COLOR_ERROR, "PDU malformed"); |
| packet_hexdump(frame->data, frame->size); |
| } |
| } |