Hi,
Here's series of patches which adds decoding of AVDTP signalling channel
and A2DP codec capabilities information. Few things are missing:
- no fragmentation support
- some rarely used capabilities are not decoded
- ATRAC capabilities are not decoded
Other that above, pretty much everything should be decoded.
Changes in v2:
- fixed formatting according to comments
- updated commit messages to include decoded frames (except for few of
them where I was not able trigger proper signalling)
- some minor fixed found during development
Andrzej Kaczmarek (22):
monitor/l2cap: Add channel sequence number
monitor/avdtp: Add basic decoding of AVDTP signalling
monitor/avdtp: Decode AVDTP_DISCOVER
monitor/avdtp: Decode AVDTP_GET_CAPABILITIES
monitor/avdtp: Decode AVDTP_SET_CONFIGURATION
monitor/avdtp: Decode AVDTP_GET_CONFIGURATION
monitor/avdtp: Decode AVDTP_RECONFIGURE
monitor/avdtp: Decode AVDTP_OPEN
monitor/avdtp: Decode AVDTP_START
monitor/avdtp: Decode AVDTP_CLOSE
monitor/avdtp: Decode AVDTP_SUSPEND
monitor/avdtp: Decode AVDTP_ABORT
monitor/avdtp: Decode AVDTP_SECURITY_CONTROL
monitor/avdtp: Decode AVDTP_GET_ALL_CAPABILITIES
monitor/avdtp: Decode AVDTP_DELAYREPORT
monitor/avdtp: Decode basic Media Codec capabilities
monitor/avdtp: Decode basic Content Protection capabilities
monitor/a2dp: Decode SBC capabilities
monitor/a2dp: Decode MPEG-1,2 capabilities
monitor/a2dp: Decode AAC capabilities
monitor/a2dp: Decode aptX capabilities
monitor/a2dp: Decode LDAC capabilities
Makefile.tools | 2 +
android/Android.mk | 2 +
monitor/a2dp.c | 640 ++++++++++++++++++++++++++++++++++++++++
monitor/a2dp.h | 26 ++
monitor/avdtp.c | 836 +++++++++++++++++++++++++++++++++++++++++++++++++++++
monitor/avdtp.h | 24 ++
monitor/l2cap.c | 48 ++-
monitor/l2cap.h | 1 +
8 files changed, 1566 insertions(+), 13 deletions(-)
create mode 100644 monitor/a2dp.c
create mode 100644 monitor/a2dp.h
create mode 100644 monitor/avdtp.c
create mode 100644 monitor/avdtp.h
--
2.6.2
Hi Andrzej,
On Friday 20 November 2015 15:13:21 Andrzej Kaczmarek wrote:
> < ACL Data TX: Handle 256 flags 0x00 dlen 6
> Channel: 258 len 2 [PSM 25 mode 0] {chan 2}
> AVDTP: Discover (0x01) Command (0x00) type 0x00 label 0 nosp 0
>
> > ACL Data RX: Handle 256 flags 0x02 dlen 14
>
> Channel: 66 len 10 [PSM 25 mode 0] {chan 2}
> AVDTP: Discover (0x01) Response Accept (0x02) type 0x00 label 0 nosp 0
> ACP SEID: 1
> Media Type: Audio (0x00)
> SEP Type: SRC (0x01)
> In use: No
> ACP SEID: 5
> Media Type: Audio (0x00)
> SEP Type: SRC (0x01)
> In use: No
> ACP SEID: 3
> Media Type: Audio (0x00)
> SEP Type: SRC (0x01)
> In use: No
> ACP SEID: 2
> Media Type: Audio (0x00)
> SEP Type: SRC (0x01)
> In use: No
> ---
> monitor/avdtp.c | 134
> +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed,
> 132 insertions(+), 2 deletions(-)
>
> diff --git a/monitor/avdtp.c b/monitor/avdtp.c
> index de4edbb..78e3c3b 100644
> --- a/monitor/avdtp.c
> +++ b/monitor/avdtp.c
> @@ -109,6 +109,115 @@ static const char *sigid2str(uint8_t sigid)
> }
> }
>
> +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 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 avdtp_discover(struct avdtp_frame *avdtp_frame)
> +{
> + struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
> +
> + if (avdtp_frame->hdr & 0x01)
> + goto reject;
> +
> + if (avdtp_frame->hdr & 0x02)
> + goto response;
> +
> + /* no extra parameters */
> + return true;
> +
> +reject:
> + return avdtp_reject_common(avdtp_frame);
> +
> +response:
> + for (;;) {
> + uint8_t seid;
> + uint8_t info;
> +
> + if (!l2cap_frame_get_u8(frame, &seid))
> + break;
> +
> + if (!l2cap_frame_get_u8(frame, &info))
> + return false;
> +
> + print_field("ACP SEID: %d", seid >> 2);
> + 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");
> + }
Maybe it is a matter of taste, but I'd not abuse goto in such simple
functions.
ie
/* reject */
if (avdtp_frame->hdr & 0x01)
return avdtp_reject_common(avdtp_frame);
/* response */
if (avdtp_frame->hdr & 0x02) {
....
return true;
}
....
return true;
> +
> + return true;
> +}
> +
> static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
> {
> struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
> @@ -116,6 +225,7 @@ static bool avdtp_signalling_packet(struct avdtp_frame
> *avdtp_frame) uint8_t hdr;
> uint8_t sig_id;
> uint8_t nosp = 0;
> + bool ret;
>
> if (frame->in)
> pdu_color = COLOR_MAGENTA;
> @@ -152,8 +262,28 @@ static bool avdtp_signalling_packet(struct avdtp_frame
> *avdtp_frame) sig_id, msgtype2str(hdr & 0x03), hdr & 0x03,
> hdr & 0x0c, hdr >> 4, nosp);
>
> - packet_hexdump(frame->data, frame->size);
> - return true;
> + /* 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:
> + ret = avdtp_discover(avdtp_frame);
> + break;
> + default:
> + packet_hexdump(frame->data, frame->size);
> + ret = true;
> + break;
> + }
> +
> + return ret;
> }
>
> void avdtp_packet(const struct l2cap_frame *frame)
--
pozdrawiam
Szymon Janc
Hi Andrzej,
On Friday 20 November 2015 15:13:40 Andrzej Kaczmarek wrote:
> > ACL Data RX: Handle 12 flags 0x02 dlen 24
>
> Channel: 67 len 20 [PSM 25 mode 0] {chan 1}
> AVDTP: Get Capabilities (0x02) Response Accept (0x02) type 0x00 label
> 2 nosp 0 Service Category: Media Transport (0x01)
> Service Category: Media Codec (0x07)
> Media Type: Audio (0x00)
> Media Codec: Non-A2DP (0xff)
> Vendor ID: Sony Corporation (0x0000012d)
> Vendor Specific Codec ID: LDAC (0x00aa)
> Unknown: 0x073c
> Service Category: Content Protection (0x04)
> Content Protection Type: SCMS-T (0x0002)
>
> < ACL Data TX: Handle 12 flags 0x02 dlen 26
> Channel: 2753 len 22 [PSM 25 mode 0] {chan 1}
> AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 5 nosp
> 0 ACP SEID: 5
> INT SEID: 3
> Service Category: Media Transport (0x01)
> Service Category: Media Codec (0x07)
> Media Type: Audio (0x00)
> Media Codec: Non-A2DP (0xff)
> Vendor ID: Sony Corporation (0x0000012d)
> Vendor Specific Codec ID: LDAC (0x00aa0
> Unknown: 0x0104
> Service Category: Content Protection (0x04)
> Content Protection Type: SCMS-T (0x0002)
> ---
> monitor/a2dp.c | 22 ++++++++++++++++++++++
> 1 file changed, 22 insertions(+)
>
> diff --git a/monitor/a2dp.c b/monitor/a2dp.c
> index d438db0..2077d70 100644
> --- a/monitor/a2dp.c
> +++ b/monitor/a2dp.c
> @@ -50,6 +50,8 @@
> /* Vendor Specific A2DP Codecs */
> #define APTX_VENDOR_ID 0x0000004f
> #define APTX_CODEC_ID 0x0001
> +#define LDAC_VENDOR_ID 0x0000012d
> +#define LDAC_CODEC_ID 0x00aa
>
> struct bit_desc {
> uint8_t bit;
> @@ -212,6 +214,8 @@ static const char *vndcodec2str(uint32_t vendor_id,
> uint16_t codec_id) {
> if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID)
> return "aptX";
> + else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID)
> + return "LDAC";
>
> return "Unknown";
> }
> @@ -503,6 +507,20 @@ static bool codec_vendor_aptx_cap(uint8_t losc, struct
> l2cap_frame *frame) return true;
> }
>
> +static bool codec_vendor_ldac(uint8_t losc, struct l2cap_frame *frame)
> +{
> + uint16_t cap = 0;
> +
> + if (losc != 2)
> + return false;
> +
> + l2cap_frame_get_le16(frame, &cap);
> +
> + print_field("%*cUnknown: 0x%04x", BASE_INDENT + 2, ' ', cap);
> +
> + return true;
> +}
> +
> static bool codec_vendor_cap(uint8_t losc, struct l2cap_frame *frame)
> {
> uint32_t vendor_id = 0;
> @@ -524,6 +542,8 @@ static bool codec_vendor_cap(uint8_t losc, struct
> l2cap_frame *frame)
>
> if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) {
> codec_vendor_aptx_cap(losc, frame);
> + } else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID) {
> + codec_vendor_ldac(losc, frame);
Should this return value from function? Otherwise you may silently ignore
data if length doesn't match.
> } else {
> packet_hexdump(frame->data, losc);
> l2cap_frame_pull(frame, frame, losc);
> @@ -573,6 +593,8 @@ static bool codec_vendor_cfg(uint8_t losc, struct
> l2cap_frame *frame)
>
> if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) {
> codec_vendor_aptx_cfg(losc, frame);
> + } else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID) {
> + codec_vendor_ldac(losc, frame);
> } else {
> packet_hexdump(frame->data, losc);
> l2cap_frame_pull(frame, frame, losc);
--
pozdrawiam
Szymon Janc
Hi,
On Fri, Nov 20, 2015 at 3:13 PM, Andrzej Kaczmarek
<[email protected]> wrote:
> Hi,
>
> Here's series of patches which adds decoding of AVDTP signalling channel
> and A2DP codec capabilities information. Few things are missing:
> - no fragmentation support
> - some rarely used capabilities are not decoded
> - ATRAC capabilities are not decoded
>
> Other that above, pretty much everything should be decoded.
>
> Changes in v2:
> - fixed formatting according to comments
> - updated commit messages to include decoded frames (except for few of
> them where I was not able trigger proper signalling)
> - some minor fixed found during development
I'll send v3 later which will fix some weird copy&pasted gotos and
for-loops used in commands decoding... I'll make this code look more
sane.
BR,
Andrzej
Hi Andrzej,
On Friday 20 November 2015 15:13:36 Andrzej Kaczmarek wrote:
> > ACL Data RX: Handle 256 flags 0x02 dlen 20
> >
> > [hci0] 9.242155
> Channel: 66 len 16 [PSM 25 mode 0] {chan 2}
> AVDTP: Get Capabilities (0x02) Response Accept (0x02) type 0x00 label
> 1 nosp 0 Service Category: Media Transport (0x01)
> Service Category: Media Codec (0x07)
> Media Type: Audio (0x00)
> Media Codec: SBC (0x00)
> Frequency: 0x30
> 44100
> 48000
> Channel Mode: 0x0f
> Mono
> Dual Channel
> Stereo
> Joint Channel
> Block Length: 0xf0
> 4
> 8
> 12
> 16
> Subbands: 0x0c
> 4
> 8
> Allocation Method: 0x03
> SNR
> Loudness
> Minimum Bitpool: 2
> Maximum Bitpool: 53
> Service Category: Content Protection (0x04)
> Content Protection Type: SCMS-T (0x0002)
>
> < ACL Data TX: Handle 256 flags 0x00 dlen 18
>
> [hci0] 9.272120 Channel: 258 len 14 [PSM 25 mode 0] {chan 2}
> AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 5 nosp
> 0 ACP SEID: 1
> INT SEID: 3
> Service Category: Media Transport (0x01)
> Service Category: Media Codec (0x07)
> Media Type: Audio (0x00)
> Media Codec: SBC (0x00)
> Frequency: 44100 (0x20)
> Channel Mode: Joint Channel (0x01)
> Block Length: 16 (0x10)
> Subbands: 8 (0x04)
> Allocation Method: Loudness (0x01)
> Minimum Bitpool: 2
> Maximum Bitpool: 53
> ---
> Makefile.tools | 1 +
> android/Android.mk | 1 +
> monitor/a2dp.c | 217
> +++++++++++++++++++++++++++++++++++++++++++++++++++++ monitor/a2dp.h |
> 26 +++++++
> monitor/avdtp.c | 18 +++--
> 5 files changed, 257 insertions(+), 6 deletions(-)
> create mode 100644 monitor/a2dp.c
> create mode 100644 monitor/a2dp.h
>
> diff --git a/Makefile.tools b/Makefile.tools
> index 387917e..46cdb53 100644
> --- a/Makefile.tools
> +++ b/Makefile.tools
> @@ -28,6 +28,7 @@ monitor_btmon_SOURCES = monitor/main.c monitor/bt.h \
> monitor/sdp.h monitor/sdp.c \
> monitor/avctp.h monitor/avctp.c \
> monitor/avdtp.h monitor/avdtp.c \
> + monitor/a2dp.h monitor/a2dp.c \
> monitor/rfcomm.h monitor/rfcomm.c \
> monitor/bnep.h monitor/bnep.c \
> monitor/uuid.h monitor/uuid.c \
> diff --git a/android/Android.mk b/android/Android.mk
> index fa1188b..38ef4aa 100644
> --- a/android/Android.mk
> +++ b/android/Android.mk
> @@ -340,6 +340,7 @@ LOCAL_SRC_FILES := \
> bluez/monitor/l2cap.c \
> bluez/monitor/avctp.c \
> bluez/monitor/avdtp.c \
> + bluez/monitor/a2dp.c \
> bluez/monitor/rfcomm.c \
> bluez/monitor/bnep.c \
> bluez/monitor/uuid.c \
> diff --git a/monitor/a2dp.c b/monitor/a2dp.c
> new file mode 100644
> index 0000000..2ef5889
> --- /dev/null
> +++ b/monitor/a2dp.c
> @@ -0,0 +1,217 @@
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2015 Andrzej Kaczmarek <[email protected]>
> + *
> + *
> + * 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 "a2dp.h"
> +
> +#define BASE_INDENT 4
> +
> +/* Codec Types */
> +#define A2DP_CODEC_SBC 0x00
> +#define A2DP_CODEC_MPEG12 0x01
> +#define A2DP_CODEC_MPEG24 0x02
> +#define A2DP_CODEC_ATRAC 0x04
> +#define A2DP_CODEC_VENDOR 0xff
> +
> +struct bit_desc {
> + uint8_t bit;
> + const char *str;
> +};
> +
> +static const struct bit_desc sbc_frequency_table[] = {
> + { 7, "16000" },
> + { 6, "32000" },
> + { 5, "44100" },
> + { 4, "48000" },
> + { }
> +};
> +
> +static const struct bit_desc sbc_channel_mode_table[] = {
> + { 3, "Mono" },
> + { 2, "Dual Channel" },
> + { 1, "Stereo" },
> + { 0, "Joint Channel" },
This should be "Joint Stereo".
> + { }
> +};
> +
> +static const struct bit_desc sbc_blocklen_table[] = {
> + { 7, "4" },
> + { 6, "8" },
> + { 5, "12" },
> + { 4, "16" },
> + { }
> +};
> +
> +static const struct bit_desc sbc_subbands_table[] = {
> + { 3, "4" },
> + { 2, "8" },
> + { }
> +};
> +
> +static const struct bit_desc sbc_allocation_table[] = {
> + { 1, "SNR" },
> + { 0, "Loudness" },
> + { }
> +};
> +
> +static void print_value_bits(uint8_t indent, uint32_t value,
> + const struct bit_desc *table)
> +{
> + int i;
> +
> + for (i = 0; table[i].str; i++) {
> + if (value & (1 << table[i].bit))
> + print_field("%*c%s", indent + 2, ' ', table[i].str);
> + }
> +}
> +
> +static const char *find_value_bit(uint32_t value,
> + const struct bit_desc *table)
> +{
> + int i;
> +
> + for (i = 0; table[i].str; i++) {
> + if (value & (1 << table[i].bit))
> + return table[i].str;
> + }
> +
> + return "Unknown";
> +}
> +
> +static bool codec_sbc_cap(uint8_t losc, struct l2cap_frame *frame)
> +{
> + uint8_t cap = 0;
> +
> + if (losc != 4)
> + return false;
> +
> + l2cap_frame_get_u8(frame, &cap);
> +
> + print_field("%*cFrequency: 0x%02x", BASE_INDENT, ' ', cap & 0xf0);
> + print_value_bits(BASE_INDENT, cap & 0xf0, sbc_frequency_table);
> +
> + print_field("%*cChannel Mode: 0x%02x", BASE_INDENT, ' ', cap & 0x0f);
> + print_value_bits(BASE_INDENT, cap & 0x0f, sbc_channel_mode_table);
> +
> + l2cap_frame_get_u8(frame, &cap);
> +
> + print_field("%*cBlock Length: 0x%02x", BASE_INDENT, ' ', cap & 0xf0);
> + print_value_bits(BASE_INDENT, cap & 0xf0, sbc_blocklen_table);
> +
> + print_field("%*cSubbands: 0x%02x", BASE_INDENT, ' ', cap & 0x0c);
> + print_value_bits(BASE_INDENT, cap & 0x0c, sbc_subbands_table);
> +
> + print_field("%*cAllocation Method: 0x%02x", BASE_INDENT, ' ',
> + cap & 0x03);
> + print_value_bits(BASE_INDENT, cap & 0x03, sbc_allocation_table);
> +
> + l2cap_frame_get_u8(frame, &cap);
> +
> + print_field("%*cMinimum Bitpool: %d", BASE_INDENT, ' ', cap);
> +
> + l2cap_frame_get_u8(frame, &cap);
> +
> + print_field("%*cMaximum Bitpool: %d", BASE_INDENT, ' ', cap);
> +
> + return true;
> +}
> +
> +static bool codec_sbc_cfg(uint8_t losc, struct l2cap_frame *frame)
> +{
> + uint8_t cap = 0;
> +
> + if (losc != 4)
> + return false;
> +
> + l2cap_frame_get_u8(frame, &cap);
> +
> + print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT, ' ',
> + find_value_bit(cap & 0xf0, sbc_frequency_table),
> + cap & 0xf0);
> +
> + print_field("%*cChannel Mode: %s (0x%02x)", BASE_INDENT, ' ',
> + find_value_bit(cap & 0x0f, sbc_channel_mode_table),
> + cap & 0x0f);
> +
> + l2cap_frame_get_u8(frame, &cap);
> +
> + print_field("%*cBlock Length: %s (0x%02x)", BASE_INDENT, ' ',
> + find_value_bit(cap & 0xf0, sbc_blocklen_table),
> + cap & 0xf0);
> +
> + print_field("%*cSubbands: %s (0x%02x)", BASE_INDENT, ' ',
> + find_value_bit(cap & 0x0c, sbc_subbands_table),
> + cap & 0x0c);
> +
> + print_field("%*cAllocation Method: %s (0x%02x)", BASE_INDENT, ' ',
> + find_value_bit(cap & 0x03, sbc_allocation_table),
> + cap & 0x03);
> +
> + l2cap_frame_get_u8(frame, &cap);
> +
> + print_field("%*cMinimum Bitpool: %d", BASE_INDENT, ' ', cap);
> +
> + l2cap_frame_get_u8(frame, &cap);
> +
> + print_field("%*cMaximum Bitpool: %d", BASE_INDENT, ' ', cap);
> +
> + return true;
> +}
> +
> +bool a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
> +{
> + switch (codec) {
> + case A2DP_CODEC_SBC:
> + return codec_sbc_cap(losc, frame);
> + default:
> + packet_hexdump(frame->data, losc);
> + l2cap_frame_pull(frame, frame, losc);
> + return true;
> + }
> +}
> +
> +bool a2dp_codec_cfg(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
> +{
> + switch (codec) {
> + case A2DP_CODEC_SBC:
> + return codec_sbc_cfg(losc, frame);
> + default:
> + packet_hexdump(frame->data, losc);
> + l2cap_frame_pull(frame, frame, losc);
> + return true;
> + }
> +}
> diff --git a/monitor/a2dp.h b/monitor/a2dp.h
> new file mode 100644
> index 0000000..72a8f1f
> --- /dev/null
> +++ b/monitor/a2dp.h
> @@ -0,0 +1,26 @@
> +/*
> + *
> + * BlueZ - Bluetooth protocol stack for Linux
> + *
> + * Copyright (C) 2015 Andrzej Kaczmarek <[email protected]>
> + *
> + *
> + * 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 + *
> + */
> +
> +bool a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame
> *frame); +
> +bool a2dp_codec_cfg(uint8_t codec, uint8_t losc, struct l2cap_frame
> *frame); diff --git a/monitor/avdtp.c b/monitor/avdtp.c
> index ed0d792..e9a355e 100644
> --- a/monitor/avdtp.c
> +++ b/monitor/avdtp.c
> @@ -37,6 +37,7 @@
> #include "display.h"
> #include "l2cap.h"
> #include "avdtp.h"
> +#include "a2dp.h"
>
> /* Signal Identifiers */
> #define AVDTP_DISCOVER 0x01
> @@ -69,6 +70,13 @@ struct avdtp_frame {
> 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) {
> @@ -286,12 +294,10 @@ static bool service_media_codec(struct avdtp_frame
> *avdtp_frame, uint8_t losc) print_field("%*cMedia Codec: %s (0x%02x)", 2, '
> ',
> mediacodec2str(codec), codec);
>
> - /* TODO: decode codec specific information */
> -
> - packet_hexdump(frame->data, losc);
> - l2cap_frame_pull(frame, frame, losc);
> -
> - return true;
> + 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)
--
pozdrawiam
Szymon Janc
< ACL Data TX: Handle 256 flags 0x00 dlen 18
Channel: 258 len 14 [PSM 25 mode 0] {chan 2}
AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 5 nosp 0
ACP SEID: 1
INT SEID: 3
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
00 00 21 15 02 35 ..!..5
> ACL Data RX: Handle 256 flags 0x02 dlen 6
Channel: 66 len 2 [PSM 25 mode 0] {chan 2}
AVDTP: Set Configuration (0x03) Response Accept (0x02) type 0x00 label 5 nosp 0
---
monitor/avdtp.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index cf11d2e..6869183 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -307,6 +307,43 @@ response:
return decode_capabilities(avdtp_frame);
}
+static bool avdtp_set_configuration(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t acp_seid, int_seid;
+ uint8_t service_cat;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &acp_seid))
+ return false;
+
+ if (!l2cap_frame_get_u8(frame, &int_seid))
+ return false;
+
+ print_field("ACP SEID: %d", acp_seid >> 2);
+ print_field("INT SEID: %d", int_seid >> 2);
+
+ return decode_capabilities(avdtp_frame);
+
+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);
+
+response:
+ /* no extra parameters */
+ return true;
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -369,6 +406,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_GET_CAPABILITIES:
ret = avdtp_get_capabilities(avdtp_frame);
break;
+ case AVDTP_SET_CONFIGURATION:
+ ret = avdtp_set_configuration(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
> ACL Data RX: Handle 256 flags 0x02 dlen 7
Channel: 66 len 3 [PSM 25 mode 0] {chan 2}
AVDTP: Abort (0x0a) Command (0x00) type 0x00 label 3 nosp 0
ACP SEID: 3
< ACL Data TX: Handle 256 flags 0x00 dlen 6
Channel: 258 len 2 [PSM 25 mode 0] {chan 2}
AVDTP: Abort (0x0a) Response Accept (0x02) type 0x00 label 3 nosp 0
---
monitor/avdtp.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index 4c3665f..3dfae0d 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -530,6 +530,26 @@ response:
return true;
}
+static bool avdtp_abort(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t seid;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ return true;
+
+response:
+ /* no extra parameters */
+ return true;
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -613,6 +633,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_SUSPEND:
ret = avdtp_suspend(avdtp_frame);
break;
+ case AVDTP_ABORT:
+ ret = avdtp_abort(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
< ACL Data TX: Handle 256 flags 0x00 dlen 7
Channel: 258 len 3 [PSM 25 mode 0] {chan 2}
AVDTP: Suspend (0x09) Command (0x00) type 0x00 label 8 nosp 0
ACP SEID: 1
> ACL Data RX: Handle 256 flags 0x02 dlen 6
Channel: 66 len 2 [PSM 25 mode 0] {chan 2}
AVDTP: Suspend (0x09) Response Accept (0x02) type 0x00 label 8 nosp 0
---
monitor/avdtp.c | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index 4db14dd..4c3665f 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -492,6 +492,44 @@ response:
return true;
}
+static bool avdtp_suspend(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t seid;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ for (;;) {
+ if (!l2cap_frame_get_u8(frame, &seid))
+ break;
+
+ print_field("ACP SEID: %d", seid >> 2);
+ }
+
+ return true;
+
+reject:
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ return avdtp_reject_common(avdtp_frame);
+
+response:
+ /* no extra parameters */
+ return true;
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -572,6 +610,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_CLOSE:
ret = avdtp_close(avdtp_frame);
break;
+ case AVDTP_SUSPEND:
+ ret = avdtp_suspend(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
< ACL Data TX: Handle 12 flags 0x02 dlen 7
Channel: 2753 len 3 [PSM 25 mode 0] {chan 1}
AVDTP: Close (0x08) Command (0x00) type 0x00 label 8 nosp 0
ACP SEID: 5
> ACL Data RX: Handle 12 flags 0x02 dlen 6
Channel: 67 len 2 [PSM 25 mode 0] {chan 1}
AVDTP: Close (0x08) Response Accept (0x02) type 0x00 label 8 nosp 0
---
monitor/avdtp.c | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index 3a6816b..4db14dd 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -466,6 +466,32 @@ response:
return true;
}
+static bool avdtp_close(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t seid;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ return true;
+
+reject:
+ return avdtp_reject_common(avdtp_frame);
+
+response:
+ /* no extra parameters */
+ return true;
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -543,6 +569,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_START:
ret = avdtp_start(avdtp_frame);
break;
+ case AVDTP_CLOSE:
+ ret = avdtp_close(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
---
monitor/avdtp.c | 36 ++++++++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index 84378a6..2dc0578 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -369,6 +369,39 @@ response:
return decode_capabilities(avdtp_frame);
}
+static bool avdtp_reconfigure(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t seid;
+ uint8_t service_cat;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ return decode_capabilities(avdtp_frame);
+
+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);
+
+response:
+ /* no extra parameters */
+ return true;
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -437,6 +470,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_GET_CONFIGURATION:
ret = avdtp_get_configuration(avdtp_frame);
break;
+ case AVDTP_RECONFIGURE:
+ ret = avdtp_reconfigure(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
> ACL Data RX: Handle 5 flags 0x02 dlen 23
Channel: 65 len 19 [PSM 25 mode 0] {chan 1}
AVDTP: Get Capabilities (0x02) Response Accept (0x02) type 0x00 label 12 nosp 0
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: Non-A2DP (0xff)
Vendor ID: APT Licensing Ltd. (0x0000004f)
Vendor Specific Codec ID: aptX (0x0001)
Frequency: 0x30
44100
48000
Channel Mode: 0x02
Stereo
Service Category: Content Protection (0x04)
Content Protection Type: SCMS-T (0x0002)
< ACL Data TX: Handle 5 flags 0x00 dlen 21
Channel: 193 len 17 [PSM 25 mode 0] {chan 1}
AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 14 nosp 0
ACP SEID: 3
INT SEID: 1
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: Non-A2DP (0xff)
Vendor ID: APT Licensing Ltd. (0x0000004f)
Vendor Specific Codec ID: aptX (0x0001)
Frequency: 48000 (0x10)
Channel Mode: Stereo (0x02)
---
monitor/a2dp.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 127 insertions(+)
diff --git a/monitor/a2dp.c b/monitor/a2dp.c
index 51fffd4..d438db0 100644
--- a/monitor/a2dp.c
+++ b/monitor/a2dp.c
@@ -47,6 +47,10 @@
#define A2DP_CODEC_ATRAC 0x04
#define A2DP_CODEC_VENDOR 0xff
+/* Vendor Specific A2DP Codecs */
+#define APTX_VENDOR_ID 0x0000004f
+#define APTX_CODEC_ID 0x0001
+
struct bit_desc {
uint8_t bit;
const char *str;
@@ -166,6 +170,20 @@ static const struct bit_desc aac_channels_table[] = {
{ }
};
+static const struct bit_desc aptx_frequency_table[] = {
+ { 7, "16000" },
+ { 6, "32000" },
+ { 5, "44100" },
+ { 4, "48000" },
+ { }
+};
+
+static const struct bit_desc aptx_channel_mode_table[] = {
+ { 0, "Mono" },
+ { 1, "Stereo" },
+ { }
+};
+
static void print_value_bits(uint8_t indent, uint32_t value,
const struct bit_desc *table)
{
@@ -190,6 +208,14 @@ static const char *find_value_bit(uint32_t value,
return "Unknown";
}
+static const char *vndcodec2str(uint32_t vendor_id, uint16_t codec_id)
+{
+ if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID)
+ return "aptX";
+
+ return "Unknown";
+}
+
static bool codec_sbc_cap(uint8_t losc, struct l2cap_frame *frame)
{
uint8_t cap = 0;
@@ -458,6 +484,103 @@ static bool codec_aac_cfg(uint8_t losc, struct l2cap_frame *frame)
return true;
}
+static bool codec_vendor_aptx_cap(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint8_t cap = 0;
+
+ if (losc != 1)
+ return false;
+
+ l2cap_frame_get_u8(frame, &cap);
+
+ print_field("%*cFrequency: 0x%02x", BASE_INDENT + 2, ' ', cap & 0xf0);
+ print_value_bits(BASE_INDENT + 2, cap & 0xf0, aptx_frequency_table);
+
+ print_field("%*cChannel Mode: 0x%02x", BASE_INDENT + 2, ' ',
+ cap & 0x0f);
+ print_value_bits(BASE_INDENT + 2, cap & 0x0f, aptx_channel_mode_table);
+
+ return true;
+}
+
+static bool codec_vendor_cap(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint32_t vendor_id = 0;
+ uint16_t codec_id = 0;
+
+ if (losc < 6)
+ return false;
+
+ l2cap_frame_get_le32(frame, &vendor_id);
+ l2cap_frame_get_le16(frame, &codec_id);
+
+ losc -= 6;
+
+ print_field("%*cVendor ID: %s (0x%08x)", BASE_INDENT, ' ',
+ bt_compidtostr(vendor_id), vendor_id);
+
+ print_field("%*cVendor Specific Codec ID: %s (0x%04x)", BASE_INDENT,
+ ' ', vndcodec2str(vendor_id, codec_id), codec_id);
+
+ if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) {
+ codec_vendor_aptx_cap(losc, frame);
+ } else {
+ packet_hexdump(frame->data, losc);
+ l2cap_frame_pull(frame, frame, losc);
+ }
+
+ return true;
+}
+
+static bool codec_vendor_aptx_cfg(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint8_t cap = 0;
+
+ if (losc != 1)
+ return false;
+
+ l2cap_frame_get_u8(frame, &cap);
+
+ print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT + 2, ' ',
+ find_value_bit(cap & 0xf0, aptx_frequency_table),
+ cap & 0xf0);
+
+ print_field("%*cChannel Mode: %s (0x%02x)", BASE_INDENT + 2, ' ',
+ find_value_bit(cap & 0x0f, aptx_channel_mode_table),
+ cap & 0x0f);
+
+ return true;
+}
+
+static bool codec_vendor_cfg(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint32_t vendor_id = 0;
+ uint16_t codec_id = 0;
+
+ if (losc < 6)
+ return false;
+
+ l2cap_frame_get_le32(frame, &vendor_id);
+ l2cap_frame_get_le16(frame, &codec_id);
+
+ losc -= 6;
+
+ print_field("%*cVendor ID: %s (0x%08x)", BASE_INDENT, ' ',
+ bt_compidtostr(vendor_id), vendor_id);
+
+ print_field("%*cVendor Specific Codec ID: %s (0x%04x)", BASE_INDENT,
+ ' ', vndcodec2str(vendor_id, codec_id), codec_id);
+
+ if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) {
+ codec_vendor_aptx_cfg(losc, frame);
+ } else {
+ packet_hexdump(frame->data, losc);
+ l2cap_frame_pull(frame, frame, losc);
+ }
+
+ return true;
+}
+
bool a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
{
switch (codec) {
@@ -467,6 +590,8 @@ bool a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
return codec_mpeg12_cap(losc, frame);
case A2DP_CODEC_MPEG24:
return codec_aac_cap(losc, frame);
+ case A2DP_CODEC_VENDOR:
+ return codec_vendor_cap(losc, frame);
default:
packet_hexdump(frame->data, losc);
l2cap_frame_pull(frame, frame, losc);
@@ -483,6 +608,8 @@ bool a2dp_codec_cfg(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
return codec_mpeg12_cfg(losc, frame);
case A2DP_CODEC_MPEG24:
return codec_aac_cfg(losc, frame);
+ case A2DP_CODEC_VENDOR:
+ return codec_vendor_cfg(losc, frame);
default:
packet_hexdump(frame->data, losc);
l2cap_frame_pull(frame, frame, losc);
--
2.6.2
---
monitor/a2dp.c | 150 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 150 insertions(+)
diff --git a/monitor/a2dp.c b/monitor/a2dp.c
index 2ef5889..effdfe7 100644
--- a/monitor/a2dp.c
+++ b/monitor/a2dp.c
@@ -88,6 +88,50 @@ static const struct bit_desc sbc_allocation_table[] = {
{ }
};
+static const struct bit_desc mpeg12_layer_table[] = {
+ { 7, "Layer I (mp1)" },
+ { 6, "Layer II (mp2)" },
+ { 5, "Layer III (mp3)" },
+ { }
+};
+
+static const struct bit_desc mpeg12_channel_mode_table[] = {
+ { 3, "Mono" },
+ { 2, "Dual Channel" },
+ { 1, "Stereo" },
+ { 0, "Joint Channel" },
+ { }
+};
+
+static const struct bit_desc mpeg12_frequency_table[] = {
+ { 5, "16000" },
+ { 4, "22050" },
+ { 3, "24000" },
+ { 2, "32000" },
+ { 1, "44100" },
+ { 0, "48000" },
+ { }
+};
+
+static const struct bit_desc mpeg12_bitrate_table[] = {
+ { 14, "1110" },
+ { 13, "1101" },
+ { 12, "1100" },
+ { 11, "1011" },
+ { 10, "1010" },
+ { 9, "1001" },
+ { 8, "1000" },
+ { 7, "0111" },
+ { 6, "0110" },
+ { 5, "0101" },
+ { 4, "0100" },
+ { 3, "0011" },
+ { 2, "0010" },
+ { 1, "0001" },
+ { 0, "0000" },
+ { }
+};
+
static void print_value_bits(uint8_t indent, uint32_t value,
const struct bit_desc *table)
{
@@ -192,11 +236,115 @@ static bool codec_sbc_cfg(uint8_t losc, struct l2cap_frame *frame)
return true;
}
+static bool codec_mpeg12_cap(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint16_t cap = 0;
+ uint8_t layer;
+ uint8_t chan;
+ uint8_t freq;
+ uint16_t bitrate;
+ bool crc, mpf, vbr;
+
+ if (losc != 4)
+ return false;
+
+ l2cap_frame_get_be16(frame, &cap);
+
+ layer = (cap >> 8) & 0xe0;
+ crc = cap & 0x1000;
+ chan = (cap >> 8) & 0x0f;
+ mpf = cap & 0x0040;
+ freq = cap & 0x003f;
+
+ l2cap_frame_get_be16(frame, &cap);
+
+ vbr = cap & 0x8000;
+ bitrate = cap & 0x7fff;
+
+ print_field("%*cLayer: 0x%02x", BASE_INDENT, ' ', layer);
+ print_value_bits(BASE_INDENT, layer, mpeg12_layer_table);
+
+ print_field("%*cCRC: %s", BASE_INDENT, ' ', crc ? "Yes" : "No");
+
+ print_field("%*cChannel Mode: 0x%02x", BASE_INDENT, ' ', chan);
+ print_value_bits(BASE_INDENT, chan, mpeg12_channel_mode_table);
+
+ print_field("%*cMedia Payload Format: %s", BASE_INDENT, ' ',
+ mpf ? "RFC-2250 RFC-3119" : "RFC-2250");
+
+ print_field("%*cFrequency: 0x%02x", BASE_INDENT, ' ', freq);
+ print_value_bits(BASE_INDENT, freq, mpeg12_frequency_table);
+
+ if (!vbr) {
+ print_field("%*cBitrate Index: 0x%04x", BASE_INDENT, ' ',
+ bitrate);
+ print_value_bits(BASE_INDENT, freq, mpeg12_bitrate_table);
+ }
+
+ print_field("%*cVBR: %s", BASE_INDENT, ' ', vbr ? "Yes" : "No");
+
+ return true;
+}
+
+static bool codec_mpeg12_cfg(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint16_t cap = 0;
+ uint8_t layer;
+ uint8_t chan;
+ uint8_t freq;
+ uint16_t bitrate;
+ bool crc, mpf, vbr;
+
+ if (losc != 4)
+ return false;
+
+ l2cap_frame_get_be16(frame, &cap);
+
+ layer = (cap >> 8) & 0xe0;
+ crc = cap & 0x1000;
+ chan = (cap >> 8) & 0x0f;
+ mpf = cap & 0x0040;
+ freq = cap & 0x003f;
+
+ l2cap_frame_get_be16(frame, &cap);
+
+ vbr = cap & 0x8000;
+ bitrate = cap & 0x7fff;
+
+ print_field("%*cLayer: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(layer, mpeg12_layer_table),
+ layer);
+
+ print_field("%*cCRC: %s", BASE_INDENT, ' ', crc ? "Yes" : "No");
+
+ print_field("%*cChannel Mode: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(chan, mpeg12_channel_mode_table),
+ chan);
+
+ print_field("%*cMedia Payload Format: %s", BASE_INDENT, ' ',
+ mpf ? "RFC-2250 RFC-3119" : "RFC-2250");
+
+ print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(freq, mpeg12_frequency_table),
+ freq);
+
+ if (!vbr)
+ print_field("%*cBitrate Index: %s (0x%04x)", BASE_INDENT, ' ',
+ find_value_bit(freq, mpeg12_bitrate_table),
+ bitrate);
+
+ print_field("%*cVBR: %s", BASE_INDENT, ' ', vbr ? "Yes" : "No");
+
+ return true;
+}
+
bool a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
{
switch (codec) {
case A2DP_CODEC_SBC:
return codec_sbc_cap(losc, frame);
+ case A2DP_CODEC_MPEG12:
+ return codec_mpeg12_cap(losc, frame);
default:
packet_hexdump(frame->data, losc);
l2cap_frame_pull(frame, frame, losc);
@@ -209,6 +357,8 @@ bool a2dp_codec_cfg(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
switch (codec) {
case A2DP_CODEC_SBC:
return codec_sbc_cfg(losc, frame);
+ case A2DP_CODEC_MPEG12:
+ return codec_mpeg12_cfg(losc, frame);
default:
packet_hexdump(frame->data, losc);
l2cap_frame_pull(frame, frame, losc);
--
2.6.2
> ACL Data RX: Handle 12 flags 0x02 dlen 24
Channel: 67 len 20 [PSM 25 mode 0] {chan 1}
AVDTP: Get Capabilities (0x02) Response Accept (0x02) type 0x00 label 2 nosp 0
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: Non-A2DP (0xff)
Vendor ID: Sony Corporation (0x0000012d)
Vendor Specific Codec ID: LDAC (0x00aa)
Unknown: 0x073c
Service Category: Content Protection (0x04)
Content Protection Type: SCMS-T (0x0002)
< ACL Data TX: Handle 12 flags 0x02 dlen 26
Channel: 2753 len 22 [PSM 25 mode 0] {chan 1}
AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 5 nosp 0
ACP SEID: 5
INT SEID: 3
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: Non-A2DP (0xff)
Vendor ID: Sony Corporation (0x0000012d)
Vendor Specific Codec ID: LDAC (0x00aa0
Unknown: 0x0104
Service Category: Content Protection (0x04)
Content Protection Type: SCMS-T (0x0002)
---
monitor/a2dp.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/monitor/a2dp.c b/monitor/a2dp.c
index d438db0..2077d70 100644
--- a/monitor/a2dp.c
+++ b/monitor/a2dp.c
@@ -50,6 +50,8 @@
/* Vendor Specific A2DP Codecs */
#define APTX_VENDOR_ID 0x0000004f
#define APTX_CODEC_ID 0x0001
+#define LDAC_VENDOR_ID 0x0000012d
+#define LDAC_CODEC_ID 0x00aa
struct bit_desc {
uint8_t bit;
@@ -212,6 +214,8 @@ static const char *vndcodec2str(uint32_t vendor_id, uint16_t codec_id)
{
if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID)
return "aptX";
+ else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID)
+ return "LDAC";
return "Unknown";
}
@@ -503,6 +507,20 @@ static bool codec_vendor_aptx_cap(uint8_t losc, struct l2cap_frame *frame)
return true;
}
+static bool codec_vendor_ldac(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint16_t cap = 0;
+
+ if (losc != 2)
+ return false;
+
+ l2cap_frame_get_le16(frame, &cap);
+
+ print_field("%*cUnknown: 0x%04x", BASE_INDENT + 2, ' ', cap);
+
+ return true;
+}
+
static bool codec_vendor_cap(uint8_t losc, struct l2cap_frame *frame)
{
uint32_t vendor_id = 0;
@@ -524,6 +542,8 @@ static bool codec_vendor_cap(uint8_t losc, struct l2cap_frame *frame)
if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) {
codec_vendor_aptx_cap(losc, frame);
+ } else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID) {
+ codec_vendor_ldac(losc, frame);
} else {
packet_hexdump(frame->data, losc);
l2cap_frame_pull(frame, frame, losc);
@@ -573,6 +593,8 @@ static bool codec_vendor_cfg(uint8_t losc, struct l2cap_frame *frame)
if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) {
codec_vendor_aptx_cfg(losc, frame);
+ } else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID) {
+ codec_vendor_ldac(losc, frame);
} else {
packet_hexdump(frame->data, losc);
l2cap_frame_pull(frame, frame, losc);
--
2.6.2
> ACL Data RX: Handle 12 flags 0x02 dlen 22 4.565591
Channel: 67 len 18 [PSM 25 mode 0] {chan 1}
AVDTP: Get Capabilities (0x02) Response Accept (0x02) type 0x00 label 4 nosp 0
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: MPEG-2,4 AAC (0x02)
Object Type: 0xc0
MPEG-2 AAC LC
MPEG-4 AAC LC
Frequency: 0x180
44100
48000
Channels: 0x0c
1
2
Bitrate: 320000bps
VBR: Yes
Service Category: Content Protection (0x04)
Content Protection Type: SCMS-T (0x0002)
---
monitor/a2dp.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 124 insertions(+)
diff --git a/monitor/a2dp.c b/monitor/a2dp.c
index effdfe7..51fffd4 100644
--- a/monitor/a2dp.c
+++ b/monitor/a2dp.c
@@ -132,6 +132,40 @@ static const struct bit_desc mpeg12_bitrate_table[] = {
{ }
};
+static const struct bit_desc aac_object_type_table[] = {
+ { 7, "MPEG-2 AAC LC" },
+ { 6, "MPEG-4 AAC LC" },
+ { 5, "MPEG-4 AAC LTP" },
+ { 4, "MPEG-4 AAC scalable" },
+ { 3, "RFA (b3)" },
+ { 2, "RFA (b2)" },
+ { 1, "RFA (b1)" },
+ { 0, "RFA (b0)" },
+ { }
+};
+
+static const struct bit_desc aac_frequency_table[] = {
+ { 15, "8000" },
+ { 14, "11025" },
+ { 13, "12000" },
+ { 12, "16000" },
+ { 11, "22050" },
+ { 10, "24000" },
+ { 9, "32000" },
+ { 8, "44100" },
+ { 7, "48000" },
+ { 6, "64000" },
+ { 5, "88200" },
+ { 4, "96000" },
+ { }
+};
+
+static const struct bit_desc aac_channels_table[] = {
+ { 3, "1" },
+ { 2, "2" },
+ { }
+};
+
static void print_value_bits(uint8_t indent, uint32_t value,
const struct bit_desc *table)
{
@@ -338,6 +372,92 @@ static bool codec_mpeg12_cfg(uint8_t losc, struct l2cap_frame *frame)
return true;
}
+static bool codec_aac_cap(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint16_t cap = 0;
+ uint8_t type;
+ uint16_t freq;
+ uint8_t chan;
+ uint32_t bitrate;
+ bool vbr;
+
+ if (losc != 6)
+ return false;
+
+ l2cap_frame_get_be16(frame, &cap);
+
+ type = cap >> 8;
+ freq = cap << 8;
+
+ l2cap_frame_get_be16(frame, &cap);
+
+ freq |= (cap >> 8) & 0xf0;
+ chan = (cap >> 8) & 0x0c;
+ bitrate = (cap << 16) & 0x7f0000;
+ vbr = cap & 0x0080;
+
+ l2cap_frame_get_be16(frame, &cap);
+
+ bitrate |= cap;
+
+ print_field("%*cObject Type: 0x%02x", BASE_INDENT, ' ', type);
+ print_value_bits(BASE_INDENT, type, aac_object_type_table);
+
+ print_field("%*cFrequency: 0x%02x", BASE_INDENT, ' ', freq);
+ print_value_bits(BASE_INDENT, freq, aac_frequency_table);
+
+ print_field("%*cChannels: 0x%02x", BASE_INDENT, ' ', chan);
+ print_value_bits(BASE_INDENT, chan, aac_channels_table);
+
+ print_field("%*cBitrate: %ubps", BASE_INDENT, ' ', bitrate);
+ print_field("%*cVBR: %s", BASE_INDENT, ' ', vbr ? "Yes" : "No");
+
+ return true;
+}
+
+static bool codec_aac_cfg(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint16_t cap = 0;
+ uint8_t type;
+ uint16_t freq;
+ uint8_t chan;
+ uint32_t bitrate;
+ bool vbr;
+
+ if (losc != 6)
+ return false;
+
+ l2cap_frame_get_be16(frame, &cap);
+
+ type = cap >> 8;
+ freq = cap << 8;
+
+ l2cap_frame_get_be16(frame, &cap);
+
+ freq |= (cap >> 8) & 0xf0;
+ chan = (cap >> 8) & 0x0c;
+ bitrate = (cap << 16) & 0x7f0000;
+ vbr = cap & 0x0080;
+
+ l2cap_frame_get_be16(frame, &cap);
+
+ bitrate |= cap;
+
+ print_field("%*cObject Type: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(type, aac_object_type_table), type);
+
+ print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(freq, aac_frequency_table), freq);
+
+ print_field("%*cChannels: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(chan, aac_channels_table), chan);
+
+ print_field("%*cBitrate: %ubps", BASE_INDENT, ' ', bitrate);
+ print_field("%*cVBR: %s", BASE_INDENT, ' ', vbr ? "Yes" : "No");
+
+ return true;
+}
+
bool a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
{
switch (codec) {
@@ -345,6 +465,8 @@ bool a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
return codec_sbc_cap(losc, frame);
case A2DP_CODEC_MPEG12:
return codec_mpeg12_cap(losc, frame);
+ case A2DP_CODEC_MPEG24:
+ return codec_aac_cap(losc, frame);
default:
packet_hexdump(frame->data, losc);
l2cap_frame_pull(frame, frame, losc);
@@ -359,6 +481,8 @@ bool a2dp_codec_cfg(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
return codec_sbc_cfg(losc, frame);
case A2DP_CODEC_MPEG12:
return codec_mpeg12_cfg(losc, frame);
+ case A2DP_CODEC_MPEG24:
+ return codec_aac_cfg(losc, frame);
default:
packet_hexdump(frame->data, losc);
l2cap_frame_pull(frame, frame, losc);
--
2.6.2
---
monitor/avdtp.c | 34 ++++++++++++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index e27dc25..a4ff421 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -577,6 +577,37 @@ response:
return true;
}
+static bool avdtp_delayreport(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t seid;
+ uint16_t delay;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ if (!l2cap_frame_get_be16(frame, &delay))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+ print_field("Delay: %d.%dms", delay / 10, delay % 10);
+
+ return true;
+
+reject:
+ return avdtp_reject_common(avdtp_frame);
+
+response:
+ /* no extra parameters */
+ return true;
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -667,6 +698,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_SECURITY_CONTROL:
ret = avdtp_security_control(avdtp_frame);
break;
+ case AVDTP_DELAYREPORT:
+ ret = avdtp_delayreport(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
> ACL Data RX: Handle 256 flags 0x02 dlen 20 [hci0] 9.242155
Channel: 66 len 16 [PSM 25 mode 0] {chan 2}
AVDTP: Get Capabilities (0x02) Response Accept (0x02) type 0x00 label 1 nosp 0
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: SBC (0x00)
Frequency: 0x30
44100
48000
Channel Mode: 0x0f
Mono
Dual Channel
Stereo
Joint Channel
Block Length: 0xf0
4
8
12
16
Subbands: 0x0c
4
8
Allocation Method: 0x03
SNR
Loudness
Minimum Bitpool: 2
Maximum Bitpool: 53
Service Category: Content Protection (0x04)
Content Protection Type: SCMS-T (0x0002)
< ACL Data TX: Handle 256 flags 0x00 dlen 18 [hci0] 9.272120
Channel: 258 len 14 [PSM 25 mode 0] {chan 2}
AVDTP: Set Configuration (0x03) Command (0x00) type 0x00 label 5 nosp 0
ACP SEID: 1
INT SEID: 3
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: SBC (0x00)
Frequency: 44100 (0x20)
Channel Mode: Joint Channel (0x01)
Block Length: 16 (0x10)
Subbands: 8 (0x04)
Allocation Method: Loudness (0x01)
Minimum Bitpool: 2
Maximum Bitpool: 53
---
Makefile.tools | 1 +
android/Android.mk | 1 +
monitor/a2dp.c | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++
monitor/a2dp.h | 26 +++++++
monitor/avdtp.c | 18 +++--
5 files changed, 257 insertions(+), 6 deletions(-)
create mode 100644 monitor/a2dp.c
create mode 100644 monitor/a2dp.h
diff --git a/Makefile.tools b/Makefile.tools
index 387917e..46cdb53 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -28,6 +28,7 @@ monitor_btmon_SOURCES = monitor/main.c monitor/bt.h \
monitor/sdp.h monitor/sdp.c \
monitor/avctp.h monitor/avctp.c \
monitor/avdtp.h monitor/avdtp.c \
+ monitor/a2dp.h monitor/a2dp.c \
monitor/rfcomm.h monitor/rfcomm.c \
monitor/bnep.h monitor/bnep.c \
monitor/uuid.h monitor/uuid.c \
diff --git a/android/Android.mk b/android/Android.mk
index fa1188b..38ef4aa 100644
--- a/android/Android.mk
+++ b/android/Android.mk
@@ -340,6 +340,7 @@ LOCAL_SRC_FILES := \
bluez/monitor/l2cap.c \
bluez/monitor/avctp.c \
bluez/monitor/avdtp.c \
+ bluez/monitor/a2dp.c \
bluez/monitor/rfcomm.c \
bluez/monitor/bnep.c \
bluez/monitor/uuid.c \
diff --git a/monitor/a2dp.c b/monitor/a2dp.c
new file mode 100644
index 0000000..2ef5889
--- /dev/null
+++ b/monitor/a2dp.c
@@ -0,0 +1,217 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2015 Andrzej Kaczmarek <[email protected]>
+ *
+ *
+ * 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 "a2dp.h"
+
+#define BASE_INDENT 4
+
+/* Codec Types */
+#define A2DP_CODEC_SBC 0x00
+#define A2DP_CODEC_MPEG12 0x01
+#define A2DP_CODEC_MPEG24 0x02
+#define A2DP_CODEC_ATRAC 0x04
+#define A2DP_CODEC_VENDOR 0xff
+
+struct bit_desc {
+ uint8_t bit;
+ const char *str;
+};
+
+static const struct bit_desc sbc_frequency_table[] = {
+ { 7, "16000" },
+ { 6, "32000" },
+ { 5, "44100" },
+ { 4, "48000" },
+ { }
+};
+
+static const struct bit_desc sbc_channel_mode_table[] = {
+ { 3, "Mono" },
+ { 2, "Dual Channel" },
+ { 1, "Stereo" },
+ { 0, "Joint Channel" },
+ { }
+};
+
+static const struct bit_desc sbc_blocklen_table[] = {
+ { 7, "4" },
+ { 6, "8" },
+ { 5, "12" },
+ { 4, "16" },
+ { }
+};
+
+static const struct bit_desc sbc_subbands_table[] = {
+ { 3, "4" },
+ { 2, "8" },
+ { }
+};
+
+static const struct bit_desc sbc_allocation_table[] = {
+ { 1, "SNR" },
+ { 0, "Loudness" },
+ { }
+};
+
+static void print_value_bits(uint8_t indent, uint32_t value,
+ const struct bit_desc *table)
+{
+ int i;
+
+ for (i = 0; table[i].str; i++) {
+ if (value & (1 << table[i].bit))
+ print_field("%*c%s", indent + 2, ' ', table[i].str);
+ }
+}
+
+static const char *find_value_bit(uint32_t value,
+ const struct bit_desc *table)
+{
+ int i;
+
+ for (i = 0; table[i].str; i++) {
+ if (value & (1 << table[i].bit))
+ return table[i].str;
+ }
+
+ return "Unknown";
+}
+
+static bool codec_sbc_cap(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint8_t cap = 0;
+
+ if (losc != 4)
+ return false;
+
+ l2cap_frame_get_u8(frame, &cap);
+
+ print_field("%*cFrequency: 0x%02x", BASE_INDENT, ' ', cap & 0xf0);
+ print_value_bits(BASE_INDENT, cap & 0xf0, sbc_frequency_table);
+
+ print_field("%*cChannel Mode: 0x%02x", BASE_INDENT, ' ', cap & 0x0f);
+ print_value_bits(BASE_INDENT, cap & 0x0f, sbc_channel_mode_table);
+
+ l2cap_frame_get_u8(frame, &cap);
+
+ print_field("%*cBlock Length: 0x%02x", BASE_INDENT, ' ', cap & 0xf0);
+ print_value_bits(BASE_INDENT, cap & 0xf0, sbc_blocklen_table);
+
+ print_field("%*cSubbands: 0x%02x", BASE_INDENT, ' ', cap & 0x0c);
+ print_value_bits(BASE_INDENT, cap & 0x0c, sbc_subbands_table);
+
+ print_field("%*cAllocation Method: 0x%02x", BASE_INDENT, ' ',
+ cap & 0x03);
+ print_value_bits(BASE_INDENT, cap & 0x03, sbc_allocation_table);
+
+ l2cap_frame_get_u8(frame, &cap);
+
+ print_field("%*cMinimum Bitpool: %d", BASE_INDENT, ' ', cap);
+
+ l2cap_frame_get_u8(frame, &cap);
+
+ print_field("%*cMaximum Bitpool: %d", BASE_INDENT, ' ', cap);
+
+ return true;
+}
+
+static bool codec_sbc_cfg(uint8_t losc, struct l2cap_frame *frame)
+{
+ uint8_t cap = 0;
+
+ if (losc != 4)
+ return false;
+
+ l2cap_frame_get_u8(frame, &cap);
+
+ print_field("%*cFrequency: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(cap & 0xf0, sbc_frequency_table),
+ cap & 0xf0);
+
+ print_field("%*cChannel Mode: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(cap & 0x0f, sbc_channel_mode_table),
+ cap & 0x0f);
+
+ l2cap_frame_get_u8(frame, &cap);
+
+ print_field("%*cBlock Length: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(cap & 0xf0, sbc_blocklen_table),
+ cap & 0xf0);
+
+ print_field("%*cSubbands: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(cap & 0x0c, sbc_subbands_table),
+ cap & 0x0c);
+
+ print_field("%*cAllocation Method: %s (0x%02x)", BASE_INDENT, ' ',
+ find_value_bit(cap & 0x03, sbc_allocation_table),
+ cap & 0x03);
+
+ l2cap_frame_get_u8(frame, &cap);
+
+ print_field("%*cMinimum Bitpool: %d", BASE_INDENT, ' ', cap);
+
+ l2cap_frame_get_u8(frame, &cap);
+
+ print_field("%*cMaximum Bitpool: %d", BASE_INDENT, ' ', cap);
+
+ return true;
+}
+
+bool a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
+{
+ switch (codec) {
+ case A2DP_CODEC_SBC:
+ return codec_sbc_cap(losc, frame);
+ default:
+ packet_hexdump(frame->data, losc);
+ l2cap_frame_pull(frame, frame, losc);
+ return true;
+ }
+}
+
+bool a2dp_codec_cfg(uint8_t codec, uint8_t losc, struct l2cap_frame *frame)
+{
+ switch (codec) {
+ case A2DP_CODEC_SBC:
+ return codec_sbc_cfg(losc, frame);
+ default:
+ packet_hexdump(frame->data, losc);
+ l2cap_frame_pull(frame, frame, losc);
+ return true;
+ }
+}
diff --git a/monitor/a2dp.h b/monitor/a2dp.h
new file mode 100644
index 0000000..72a8f1f
--- /dev/null
+++ b/monitor/a2dp.h
@@ -0,0 +1,26 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2015 Andrzej Kaczmarek <[email protected]>
+ *
+ *
+ * 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
+ *
+ */
+
+bool a2dp_codec_cap(uint8_t codec, uint8_t losc, struct l2cap_frame *frame);
+
+bool a2dp_codec_cfg(uint8_t codec, uint8_t losc, struct l2cap_frame *frame);
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index ed0d792..e9a355e 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -37,6 +37,7 @@
#include "display.h"
#include "l2cap.h"
#include "avdtp.h"
+#include "a2dp.h"
/* Signal Identifiers */
#define AVDTP_DISCOVER 0x01
@@ -69,6 +70,13 @@ struct avdtp_frame {
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) {
@@ -286,12 +294,10 @@ static bool service_media_codec(struct avdtp_frame *avdtp_frame, uint8_t losc)
print_field("%*cMedia Codec: %s (0x%02x)", 2, ' ',
mediacodec2str(codec), codec);
- /* TODO: decode codec specific information */
-
- packet_hexdump(frame->data, losc);
- l2cap_frame_pull(frame, frame, losc);
-
- return true;
+ 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)
--
2.6.2
> ACL Data RX: Handle 256 flags 0x02 dlen 20
Channel: 66 len 16 [PSM 25 mode 0] {chan 2}
AVDTP: Get Capabilities (0x02) Response Accept (0x02) type 0x00 label 1 nosp 0
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: SBC (0x00)
3f ff 02 35 ?..5
Service Category: Content Protection (0x04)
Content Protection Type: SCMS-T (0x0002)
---
monitor/avdtp.c | 41 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 40 insertions(+), 1 deletion(-)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index 2e94558..ed0d792 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -193,6 +193,18 @@ static const char *mediacodec2str(uint8_t codec)
}
}
+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) {
@@ -230,6 +242,30 @@ static bool avdtp_reject_common(struct avdtp_frame *avdtp_frame)
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 */
+
+ 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;
@@ -279,6 +315,10 @@ static bool decode_capabilities(struct avdtp_frame *avdtp_frame)
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;
@@ -286,7 +326,6 @@ static bool decode_capabilities(struct avdtp_frame *avdtp_frame)
case AVDTP_MEDIA_TRANSPORT:
case AVDTP_REPORTING:
case AVDTP_RECOVERY:
- case AVDTP_CONTENT_PROTECTION:
case AVDTP_HEADER_COMPRESSION:
case AVDTP_MULTIPLEXING:
case AVDTP_DELAY_REPORTING:
--
2.6.2
< ACL Data TX: Handle 256 flags 0x00 dlen 7
Channel: 832 len 3 [PSM 25 mode 0] {chan 0}
AVDTP: Get All Capabilities (0x0c) Command (0x00) type 0x00 label 1 nosp 0
ACP SEID: 1
> ACL Data RX: Handle 256 flags 0x02 dlen 20
Channel: 64 len 16 [PSM 25 mode 0] {chan 0}
AVDTP: Get All Capabilities (0x0c) Response Accept (0x02) type 0x00 label 1 nosp 0
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
00 00 3f ff 02 35 ..?..5
Service Category: Content Protection (0x04)
02 00 ..
---
monitor/avdtp.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index bb2409e..e27dc25 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -637,6 +637,7 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
ret = avdtp_discover(avdtp_frame);
break;
case AVDTP_GET_CAPABILITIES:
+ case AVDTP_GET_ALL_CAPABILITIES:
ret = avdtp_get_capabilities(avdtp_frame);
break;
case AVDTP_SET_CONFIGURATION:
--
2.6.2
> ACL Data RX: Handle 256 flags 0x02 dlen 20
Channel: 66 len 16 [PSM 25 mode 0] {chan 2}
AVDTP: Get Capabilities (0x02) Response Accept (0x02) type 0x00 label 1 nosp 0
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
Media Type: Audio (0x00)
Media Codec: SBC (0x00)
3f ff 02 35 ?..5
Service Category: Content Protection (0x04)
02 00 ..
---
monitor/avdtp.c | 66 +++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 62 insertions(+), 4 deletions(-)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index a4ff421..2e94558 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -175,6 +175,24 @@ static const char *mediatype2str(uint8_t media_type)
}
}
+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 *servicecat2str(uint8_t service_cat)
{
switch (service_cat) {
@@ -212,6 +230,34 @@ static bool avdtp_reject_common(struct avdtp_frame *avdtp_frame)
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);
+
+ /* TODO: decode codec specific information */
+
+ packet_hexdump(frame->data, losc);
+ l2cap_frame_pull(frame, frame, losc);
+
+ return true;
+}
+
static bool decode_capabilities(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -232,11 +278,23 @@ static bool decode_capabilities(struct avdtp_frame *avdtp_frame)
if (frame->size < losc)
return false;
- /* TODO: decode service capabilities */
-
- packet_hexdump(frame->data, losc);
+ switch (service_cat) {
+ 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_CONTENT_PROTECTION:
+ case AVDTP_HEADER_COMPRESSION:
+ case AVDTP_MULTIPLEXING:
+ case AVDTP_DELAY_REPORTING:
+ default:
+ packet_hexdump(frame->data, losc);
+ l2cap_frame_pull(frame, frame, losc);
+ }
- l2cap_frame_pull(frame, frame, losc);
}
return true;
--
2.6.2
< ACL Data TX: Handle 256 flags 0x00 dlen 7
Channel: 258 len 3 [PSM 25 mode 0] {chan 2}
AVDTP: Open (0x06) Command (0x00) type 0x00 label 6 nosp 0
ACP SEID: 1
> ACL Data RX: Handle 256 flags 0x02 dlen 6
Channel: 66 len 2 [PSM 25 mode 0] {chan 2}
AVDTP: Open (0x06) Response Accept (0x02) type 0x00 label 6 nosp 0
---
monitor/avdtp.c | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index 2dc0578..cc51587 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -402,6 +402,32 @@ response:
return true;
}
+static bool avdtp_open(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t seid;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ return true;
+
+reject:
+ return avdtp_reject_common(avdtp_frame);
+
+response:
+ /* no extra parameters */
+ return true;
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -473,6 +499,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_RECONFIGURE:
ret = avdtp_reconfigure(avdtp_frame);
break;
+ case AVDTP_OPEN:
+ ret = avdtp_open(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
---
monitor/avdtp.c | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index 3dfae0d..bb2409e 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -550,6 +550,33 @@ response:
return true;
}
+static bool avdtp_security_control(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t seid;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ packet_hexdump(frame->data, frame->size);
+ return true;
+
+reject:
+ return avdtp_reject_common(avdtp_frame);
+
+response:
+ packet_hexdump(frame->data, frame->size);
+ return true;
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -636,6 +663,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_ABORT:
ret = avdtp_abort(avdtp_frame);
break;
+ case AVDTP_SECURITY_CONTROL:
+ ret = avdtp_security_control(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
< ACL Data TX: Handle 256 flags 0x00 dlen 7
Channel: 258 len 3 [PSM 25 mode 0] {chan 2}
AVDTP: Start (0x07) Command (0x00) type 0x00 label 7 nosp 0
ACP SEID: 1
> ACL Data RX: Handle 256 flags 0x02 dlen 6
Channel: 66 len 2 [PSM 25 mode 0] {chan 2}
AVDTP: Start (0x07) Response Accept (0x02) type 0x00 label 7 nosp 0
---
monitor/avdtp.c | 41 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index cc51587..3a6816b 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -428,6 +428,44 @@ response:
return true;
}
+static bool avdtp_start(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t seid;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ for (;;) {
+ if (!l2cap_frame_get_u8(frame, &seid))
+ break;
+
+ print_field("ACP SEID: %d", seid >> 2);
+ }
+
+ return true;
+
+reject:
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ return avdtp_reject_common(avdtp_frame);
+
+response:
+ /* no extra parameters */
+ return true;
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -502,6 +540,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_OPEN:
ret = avdtp_open(avdtp_frame);
break;
+ case AVDTP_START:
+ ret = avdtp_start(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
< ACL Data TX: Handle 256 flags 0x00 dlen 6
Channel: 258 len 2 [PSM 25 mode 0] {chan 2}
AVDTP: Discover (0x01) Command (0x00) type 0x00 label 0 nosp 0
> ACL Data RX: Handle 256 flags 0x02 dlen 14
Channel: 66 len 10 [PSM 25 mode 0] {chan 2}
AVDTP: Discover (0x01) Response Accept (0x02) type 0x00 label 0 nosp 0
ACP SEID: 1
Media Type: Audio (0x00)
SEP Type: SRC (0x01)
In use: No
ACP SEID: 5
Media Type: Audio (0x00)
SEP Type: SRC (0x01)
In use: No
ACP SEID: 3
Media Type: Audio (0x00)
SEP Type: SRC (0x01)
In use: No
ACP SEID: 2
Media Type: Audio (0x00)
SEP Type: SRC (0x01)
In use: No
---
monitor/avdtp.c | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 132 insertions(+), 2 deletions(-)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index de4edbb..78e3c3b 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -109,6 +109,115 @@ static const char *sigid2str(uint8_t sigid)
}
}
+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 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 avdtp_discover(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ /* no extra parameters */
+ return true;
+
+reject:
+ return avdtp_reject_common(avdtp_frame);
+
+response:
+ for (;;) {
+ uint8_t seid;
+ uint8_t info;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ break;
+
+ if (!l2cap_frame_get_u8(frame, &info))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+ 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;
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -116,6 +225,7 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
uint8_t hdr;
uint8_t sig_id;
uint8_t nosp = 0;
+ bool ret;
if (frame->in)
pdu_color = COLOR_MAGENTA;
@@ -152,8 +262,28 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
sig_id, msgtype2str(hdr & 0x03), hdr & 0x03,
hdr & 0x0c, hdr >> 4, nosp);
- packet_hexdump(frame->data, frame->size);
- return true;
+ /* 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:
+ ret = avdtp_discover(avdtp_frame);
+ break;
+ default:
+ packet_hexdump(frame->data, frame->size);
+ ret = true;
+ break;
+ }
+
+ return ret;
}
void avdtp_packet(const struct l2cap_frame *frame)
--
2.6.2
< ACL Data TX: Handle 256 flags 0x00 dlen 7
Channel: 258 len 3 [PSM 25 mode 0] {chan 2}
AVDTP: Get Capabilities (0x02) Command (0x00) type 0x00 label 1 nosp 0
ACP SEID: 1
> ACL Data RX: Handle 256 flags 0x02 dlen 20
Channel: 66 len 16 [PSM 25 mode 0] {chan 2}
AVDTP: Get Capabilities (0x02) Response Accept (0x02) type 0x00 label 1 nosp 0
Service Category: Media Transport (0x01)
Service Category: Media Codec (0x07)
00 00 3f ff 02 35 ..?..5
Service Category: Content Protection (0x04)
02 00 ..
---
monitor/avdtp.c | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 92 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index 78e3c3b..cf11d2e 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -53,6 +53,16 @@
#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;
@@ -165,6 +175,30 @@ static const char *mediatype2str(uint8_t media_type)
}
}
+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;
@@ -178,6 +212,36 @@ static bool avdtp_reject_common(struct avdtp_frame *avdtp_frame)
return true;
}
+static bool decode_capabilities(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+
+ for (;;) {
+ uint8_t service_cat;
+ uint8_t losc;
+
+ if (!l2cap_frame_get_u8(frame, &service_cat))
+ break;
+
+ if (!l2cap_frame_get_u8(frame, &losc))
+ return false;
+
+ print_field("Service Category: %s (0x%02x)",
+ servicecat2str(service_cat), service_cat);
+
+ if (frame->size < losc)
+ return false;
+
+ /* TODO: decode service capabilities */
+
+ 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;
@@ -218,6 +282,31 @@ response:
return true;
}
+static bool avdtp_get_capabilities(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t seid;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ return true;
+
+reject:
+ return avdtp_reject_common(avdtp_frame);
+
+response:
+ return decode_capabilities(avdtp_frame);
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -277,6 +366,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_DISCOVER:
ret = avdtp_discover(avdtp_frame);
break;
+ case AVDTP_GET_CAPABILITIES:
+ ret = avdtp_get_capabilities(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
---
monitor/avdtp.c | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
index 6869183..84378a6 100644
--- a/monitor/avdtp.c
+++ b/monitor/avdtp.c
@@ -344,6 +344,31 @@ response:
return true;
}
+static bool avdtp_get_configuration(struct avdtp_frame *avdtp_frame)
+{
+ struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
+ uint8_t seid;
+
+ if (avdtp_frame->hdr & 0x01)
+ goto reject;
+
+ if (avdtp_frame->hdr & 0x02)
+ goto response;
+
+ if (!l2cap_frame_get_u8(frame, &seid))
+ return false;
+
+ print_field("ACP SEID: %d", seid >> 2);
+
+ return true;
+
+reject:
+ return avdtp_reject_common(avdtp_frame);
+
+response:
+ return decode_capabilities(avdtp_frame);
+}
+
static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
{
struct l2cap_frame *frame = &avdtp_frame->l2cap_frame;
@@ -409,6 +434,9 @@ static bool avdtp_signalling_packet(struct avdtp_frame *avdtp_frame)
case AVDTP_SET_CONFIGURATION:
ret = avdtp_set_configuration(avdtp_frame);
break;
+ case AVDTP_GET_CONFIGURATION:
+ ret = avdtp_get_configuration(avdtp_frame);
+ break;
default:
packet_hexdump(frame->data, frame->size);
ret = true;
--
2.6.2
< ACL Data TX: Handle 256 flags 0x00 dlen 6
Channel: 258 len 2 [PSM 25 mode 0] {chan 2}
AVDTP: Discover (0x01) Command (0x00) type 0x00 label 0 nosp 0
> ACL Data RX: Handle 256 flags 0x02 dlen 14
Channel: 66 len 10 [PSM 25 mode 0] {chan 2}
AVDTP: Discover (0x01) Response Accept (0x02) type 0x00 label 0 nosp 0
04 08 14 08 0c 08 08 08 ........
---
Makefile.tools | 1 +
android/Android.mk | 1 +
monitor/avdtp.c | 179 +++++++++++++++++++++++++++++++++++++++++++++++++++++
monitor/avdtp.h | 24 +++++++
monitor/l2cap.c | 4 ++
5 files changed, 209 insertions(+)
create mode 100644 monitor/avdtp.c
create mode 100644 monitor/avdtp.h
diff --git a/Makefile.tools b/Makefile.tools
index d849bd9..387917e 100644
--- a/Makefile.tools
+++ b/Makefile.tools
@@ -27,6 +27,7 @@ monitor_btmon_SOURCES = monitor/main.c monitor/bt.h \
monitor/l2cap.h monitor/l2cap.c \
monitor/sdp.h monitor/sdp.c \
monitor/avctp.h monitor/avctp.c \
+ monitor/avdtp.h monitor/avdtp.c \
monitor/rfcomm.h monitor/rfcomm.c \
monitor/bnep.h monitor/bnep.c \
monitor/uuid.h monitor/uuid.c \
diff --git a/android/Android.mk b/android/Android.mk
index 694a94e..fa1188b 100644
--- a/android/Android.mk
+++ b/android/Android.mk
@@ -339,6 +339,7 @@ LOCAL_SRC_FILES := \
bluez/monitor/packet.c \
bluez/monitor/l2cap.c \
bluez/monitor/avctp.c \
+ bluez/monitor/avdtp.c \
bluez/monitor/rfcomm.c \
bluez/monitor/bnep.c \
bluez/monitor/uuid.c \
diff --git a/monitor/avdtp.c b/monitor/avdtp.c
new file mode 100644
index 0000000..de4edbb
--- /dev/null
+++ b/monitor/avdtp.c
@@ -0,0 +1,179 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2015 Andrzej Kaczmarek <[email protected]>
+ *
+ *
+ * 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"
+
+/* 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
+
+struct avdtp_frame {
+ uint8_t hdr;
+ uint8_t sig_id;
+ struct l2cap_frame l2cap_frame;
+};
+
+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 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);
+
+ 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);
+ }
+}
diff --git a/monitor/avdtp.h b/monitor/avdtp.h
new file mode 100644
index 0000000..f77d82e
--- /dev/null
+++ b/monitor/avdtp.h
@@ -0,0 +1,24 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2015 Andrzej Kaczmarek <[email protected]>
+ *
+ *
+ * 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
+ *
+ */
+
+void avdtp_packet(const struct l2cap_frame *frame);
diff --git a/monitor/l2cap.c b/monitor/l2cap.c
index de86e64..ba8feb7 100644
--- a/monitor/l2cap.c
+++ b/monitor/l2cap.c
@@ -42,6 +42,7 @@
#include "keys.h"
#include "sdp.h"
#include "avctp.h"
+#include "avdtp.h"
#include "rfcomm.h"
#include "bnep.h"
@@ -3098,6 +3099,9 @@ static void l2cap_frame(uint16_t index, bool in, uint16_t handle,
case 0x001B:
avctp_packet(&frame);
break;
+ case 0x0019:
+ avdtp_packet(&frame);
+ break;
default:
packet_hexdump(data, size);
break;
--
2.6.2
This patch adds sequence number to channels structure which determines
order in which channels for the same PSM were created. It will be used
for protocols like AVDTP where there is single PSM used for multiple
channels and order it which they were created is important.
---
monitor/l2cap.c | 44 +++++++++++++++++++++++++++++++-------------
monitor/l2cap.h | 1 +
2 files changed, 32 insertions(+), 13 deletions(-)
diff --git a/monitor/l2cap.c b/monitor/l2cap.c
index 894c741..de86e64 100644
--- a/monitor/l2cap.c
+++ b/monitor/l2cap.c
@@ -99,6 +99,7 @@ struct chan_data {
uint8_t ctrlid;
uint8_t mode;
uint8_t ext_ctrl;
+ uint8_t seq_num;
};
static struct chan_data chan_list[MAX_CHAN];
@@ -107,10 +108,13 @@ static void assign_scid(const struct l2cap_frame *frame,
uint16_t scid, uint16_t psm, uint8_t ctrlid)
{
int i, n = -1;
+ uint8_t seq_num = 1;
for (i = 0; i < MAX_CHAN; i++) {
- if (n < 0 && chan_list[i].handle == 0x0000)
+ if (n < 0 && chan_list[i].handle == 0x0000) {
n = i;
+ continue;
+ }
if (chan_list[i].index != frame->index)
continue;
@@ -118,15 +122,16 @@ static void assign_scid(const struct l2cap_frame *frame,
if (chan_list[i].handle != frame->handle)
continue;
+ if (chan_list[i].psm == psm)
+ seq_num++;
+
if (frame->in) {
if (chan_list[i].dcid == scid) {
n = i;
- break;
}
} else {
if (chan_list[i].scid == scid) {
n = i;
- break;
}
}
}
@@ -147,6 +152,8 @@ static void assign_scid(const struct l2cap_frame *frame,
chan_list[n].psm = psm;
chan_list[n].ctrlid = ctrlid;
chan_list[n].mode = 0;
+
+ chan_list[n].seq_num = seq_num;
}
static void release_scid(const struct l2cap_frame *frame, uint16_t scid)
@@ -301,6 +308,16 @@ static uint16_t get_chan(const struct l2cap_frame *frame)
return i;
}
+static uint8_t get_seq_num(const struct l2cap_frame *frame)
+{
+ int i = get_chan_data_index(frame);
+
+ if (i < 0)
+ return 0;
+
+ return chan_list[i].seq_num;
+}
+
static void assign_ext_ctrl(const struct l2cap_frame *frame,
uint8_t ext_ctrl, uint16_t dcid)
{
@@ -1384,16 +1401,17 @@ static void l2cap_frame_init(struct l2cap_frame *frame, uint16_t index, bool in,
uint16_t handle, uint8_t ident,
uint16_t cid, const void *data, uint16_t size)
{
- frame->index = index;
- frame->in = in;
- frame->handle = handle;
- frame->ident = ident;
- frame->cid = cid;
- frame->data = data;
- frame->size = size;
- frame->psm = get_psm(frame);
- frame->mode = get_mode(frame);
- frame->chan = get_chan(frame);
+ frame->index = index;
+ frame->in = in;
+ frame->handle = handle;
+ frame->ident = ident;
+ frame->cid = cid;
+ frame->data = data;
+ frame->size = size;
+ frame->psm = get_psm(frame);
+ frame->mode = get_mode(frame);
+ frame->chan = get_chan(frame);
+ frame->seq_num = get_seq_num(frame);
}
static void bredr_sig_packet(uint16_t index, bool in, uint16_t handle,
diff --git a/monitor/l2cap.h b/monitor/l2cap.h
index 0364454..813c793 100644
--- a/monitor/l2cap.h
+++ b/monitor/l2cap.h
@@ -34,6 +34,7 @@ struct l2cap_frame {
uint16_t psm;
uint16_t chan;
uint8_t mode;
+ uint8_t seq_num;
const void *data;
uint16_t size;
};
--
2.6.2