From 3c5fd713ee0bbdfda810694935a3aace5281b31c Mon Sep 17 00:00:00 2001 From: Bastien Nocera Date: Fri, 7 Jun 2019 09:10:08 +0200 Subject: [PATCH] + bluez-5.50-8 Backport loads of fixes from upstream, including: dbus-broker support (#1711594) a2dp codecs discovery discoverability filter support (used in gnome-bluetooth, #1583442) sixaxis pairing fixes --- ...emove-deprecated-at_console-statemen.patch | 43 + bluez-5.50-a2dp-backports.patch | 4714 +++++++++++++++++ bluez-5.50-autopair-backports.patch | 81 + bluez-5.50-discoverability-backports.patch | 1243 +++++ bluez-5.50-sixaxis-fixes.patch | 60 + bluez.spec | 22 +- 6 files changed, 6162 insertions(+), 1 deletion(-) create mode 100644 0001-bluetooth.conf-remove-deprecated-at_console-statemen.patch create mode 100644 bluez-5.50-a2dp-backports.patch create mode 100644 bluez-5.50-autopair-backports.patch create mode 100644 bluez-5.50-discoverability-backports.patch create mode 100644 bluez-5.50-sixaxis-fixes.patch diff --git a/0001-bluetooth.conf-remove-deprecated-at_console-statemen.patch b/0001-bluetooth.conf-remove-deprecated-at_console-statemen.patch new file mode 100644 index 0000000..e73b931 --- /dev/null +++ b/0001-bluetooth.conf-remove-deprecated-at_console-statemen.patch @@ -0,0 +1,43 @@ +From 74174217fc3f15255be875659d1e1d9403646bba Mon Sep 17 00:00:00 2001 +From: Tom Gundersen +Date: Tue, 6 Nov 2018 12:07:17 +0100 +Subject: [PATCH] bluetooth.conf: remove deprecated at_console statement + +As described in [0], this likely did not have the intended effect, so +simply remove it. The change in behavior is that up until this patch +it would be possible for root, lp, and any non-system user to potentially +gain access to bluez' dbus interface. Now this is extended to also allow +any system user. + +[0]: +--- + src/bluetooth.conf | 12 +----------- + 1 file changed, 1 insertion(+), 11 deletions(-) + +diff --git a/src/bluetooth.conf b/src/bluetooth.conf +index 0c0b221bb..05f407d1a 100644 +--- a/src/bluetooth.conf ++++ b/src/bluetooth.conf +@@ -21,18 +21,8 @@ + + + +- +- +- +- +- +- +- +- +- + +- ++ + + + +-- +2.21.0 + diff --git a/bluez-5.50-a2dp-backports.patch b/bluez-5.50-a2dp-backports.patch new file mode 100644 index 0000000..d8fbb4d --- /dev/null +++ b/bluez-5.50-a2dp-backports.patch @@ -0,0 +1,4714 @@ +From 1e9dafa0dd062f6773481074d4a426ae4c6e2aea Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:40:12 +0100 +Subject: [PATCH 01/31] avinfo: Fix buffer overflow when parsing + broken/malicious data + +Check size of buffer prior casting it to struct. +--- + tools/avinfo.c | 91 +++++++++++++++++++++++++++++++++++++++----------- + 1 file changed, 71 insertions(+), 20 deletions(-) + +diff --git a/tools/avinfo.c b/tools/avinfo.c +index 31c4e106e..47fa1d2c5 100644 +--- a/tools/avinfo.c ++++ b/tools/avinfo.c +@@ -167,10 +167,15 @@ struct avdtp_content_protection_capability { + uint8_t data[0]; + } __attribute__ ((packed)); + +-static void print_aptx(a2dp_aptx_t *aptx) ++static void print_aptx(a2dp_aptx_t *aptx, uint8_t size) + { + printf("\t\tVendor Specific Value (aptX)"); + ++ if (size < sizeof(*aptx)) { ++ printf(" (broken)\n"); ++ return; ++ } ++ + printf("\n\t\t\tFrequencies: "); + if (aptx->frequency & APTX_SAMPLING_FREQ_16000) + printf("16kHz "); +@@ -190,20 +195,33 @@ static void print_aptx(a2dp_aptx_t *aptx) + printf("\n"); + } + +-static void print_ldac(a2dp_ldac_t *ldac) ++static void print_ldac(a2dp_ldac_t *ldac, uint8_t size) + { + printf("\t\tVendor Specific Value (LDAC)"); + ++ if (size < sizeof(*ldac)) { ++ printf(" (broken)\n"); ++ return; ++ } ++ + printf("\n\t\t\tUnknown: %02x %02x", ldac->unknown[0], + ldac->unknown[1]); + + printf("\n"); + } + +-static void print_vendor(a2dp_vendor_codec_t *vendor) ++static void print_vendor(a2dp_vendor_codec_t *vendor, uint8_t size) + { +- uint32_t vendor_id = btohl(vendor->vendor_id); +- uint16_t codec_id = btohs(vendor->codec_id); ++ uint32_t vendor_id; ++ uint16_t codec_id; ++ ++ if (size < sizeof(*vendor)) { ++ printf("\tMedia Codec: Vendor Specific A2DP Codec (broken)"); ++ return; ++ } ++ ++ vendor_id = btohl(vendor->vendor_id); ++ codec_id = btohs(vendor->codec_id); + + printf("\tMedia Codec: Vendor Specific A2DP Codec"); + +@@ -212,15 +230,22 @@ static void print_vendor(a2dp_vendor_codec_t *vendor) + printf("\n\t\tVendor Specific Codec ID 0x%04x\n", codec_id); + + if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) +- print_aptx((void *) vendor); ++ print_aptx((void *) vendor, size); + else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID) +- print_ldac((void *) vendor); ++ print_ldac((void *) vendor, size); + } + +-static void print_mpeg24(a2dp_aac_t *aac) ++static void print_mpeg24(a2dp_aac_t *aac, uint8_t size) + { +- unsigned freq = AAC_GET_FREQUENCY(*aac); +- unsigned bitrate = AAC_GET_BITRATE(*aac); ++ unsigned int freq, bitrate; ++ ++ if (size < sizeof(*aac)) { ++ printf("\tMedia Codec: MPEG24 (broken)\n"); ++ return; ++ } ++ ++ freq = AAC_GET_FREQUENCY(*aac); ++ bitrate = AAC_GET_BITRATE(*aac); + + printf("\tMedia Codec: MPEG24\n\t\tObject Types: "); + +@@ -270,8 +295,13 @@ static void print_mpeg24(a2dp_aac_t *aac) + printf("\n\t\tVBR: %s", aac->vbr ? "Yes\n" : "No\n"); + } + +-static void print_mpeg12(a2dp_mpeg_t *mpeg) ++static void print_mpeg12(a2dp_mpeg_t *mpeg, uint8_t size) + { ++ if (size < sizeof(*mpeg)) { ++ printf("\tMedia Codec: MPEG12 (broken)\n"); ++ return; ++ } ++ + printf("\tMedia Codec: MPEG12\n\t\tChannel Modes: "); + + if (mpeg->channel_mode & MPEG_CHANNEL_MODE_MONO) +@@ -351,8 +381,13 @@ static void print_mpeg12(a2dp_mpeg_t *mpeg) + printf("RFC-2250\n"); + } + +-static void print_sbc(a2dp_sbc_t *sbc) ++static void print_sbc(a2dp_sbc_t *sbc, uint8_t size) + { ++ if (size < sizeof(*sbc)) { ++ printf("\tMedia Codec: SBC (broken)\n"); ++ return; ++ } ++ + printf("\tMedia Codec: SBC\n\t\tChannel Modes: "); + + if (sbc->channel_mode & SBC_CHANNEL_MODE_MONO) +@@ -394,20 +429,27 @@ static void print_sbc(a2dp_sbc_t *sbc) + sbc->min_bitpool, sbc->max_bitpool); + } + +-static void print_media_codec(struct avdtp_media_codec_capability *cap) ++static void print_media_codec( ++ struct avdtp_media_codec_capability *cap, ++ uint8_t size) + { ++ if (size < sizeof(*cap)) { ++ printf("\tMedia Codec: Unknown (broken)\n"); ++ return; ++ } ++ + switch (cap->media_codec_type) { + case A2DP_CODEC_SBC: +- print_sbc((void *) cap->data); ++ print_sbc((void *) cap->data, size - 2); + break; + case A2DP_CODEC_MPEG12: +- print_mpeg12((void *) cap->data); ++ print_mpeg12((void *) cap->data, size - 2); + break; + case A2DP_CODEC_MPEG24: +- print_mpeg24((void *) cap->data); ++ print_mpeg24((void *) cap->data, size - 2); + break; + case A2DP_CODEC_VENDOR: +- print_vendor((void *) cap->data); ++ print_vendor((void *) cap->data, size - 2); + break; + default: + printf("\tMedia Codec: Unknown\n"); +@@ -415,10 +457,16 @@ static void print_media_codec(struct avdtp_media_codec_capability *cap) + } + + static void print_content_protection( +- struct avdtp_content_protection_capability *cap) ++ struct avdtp_content_protection_capability *cap, ++ uint8_t size) + { + printf("\tContent Protection: "); + ++ if (size < sizeof(*cap)) { ++ printf("Unknown (broken)\n"); ++ return; ++ } ++ + switch (btohs(cap->content_protection_type)) { + case AVDTP_CONTENT_PROTECTION_TYPE_DTCP: + printf("DTCP"); +@@ -452,13 +500,16 @@ static void print_caps(void *data, int size) + case AVDTP_REPORTING: + case AVDTP_RECOVERY: + case AVDTP_MULTIPLEXING: ++ default: + /* FIXME: Add proper functions */ ++ printf("\tUnknown category: %d\n", cap->category); + break; + case AVDTP_MEDIA_CODEC: +- print_media_codec((void *) cap->data); ++ print_media_codec((void *) cap->data, cap->length); + break; + case AVDTP_CONTENT_PROTECTION: +- print_content_protection((void *) cap->data); ++ print_content_protection((void *) cap->data, ++ cap->length); + break; + } + +-- +2.21.0 + + +From 5ac3a1beaffcd184767fb767b131375976e7a5d9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:40:13 +0100 +Subject: [PATCH 02/31] avinfo: Show Vendor Specific Data + +--- + tools/avinfo.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/tools/avinfo.c b/tools/avinfo.c +index 47fa1d2c5..61bcdab0b 100644 +--- a/tools/avinfo.c ++++ b/tools/avinfo.c +@@ -214,6 +214,7 @@ static void print_vendor(a2dp_vendor_codec_t *vendor, uint8_t size) + { + uint32_t vendor_id; + uint16_t codec_id; ++ int i; + + if (size < sizeof(*vendor)) { + printf("\tMedia Codec: Vendor Specific A2DP Codec (broken)"); +@@ -227,7 +228,12 @@ static void print_vendor(a2dp_vendor_codec_t *vendor, uint8_t size) + + printf("\n\t\tVendor ID 0x%08x", vendor_id); + +- printf("\n\t\tVendor Specific Codec ID 0x%04x\n", codec_id); ++ printf("\n\t\tVendor Specific Codec ID 0x%04x", codec_id); ++ ++ printf("\n\t\tVendor Specific Data:"); ++ for (i = 6; i < size; ++i) ++ printf(" 0x%.02x", ((unsigned char *)vendor)[i]); ++ printf("\n"); + + if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) + print_aptx((void *) vendor, size); +-- +2.21.0 + + +From 51da4ed2a5b7cd05032faf5d7a695eecaf79f0f0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:40:14 +0100 +Subject: [PATCH 03/31] a2dp-codecs: Add SBC prefix for MIN/MAX_BITPOOL + constants + +Those two constants are SBC codec specific. +--- + android/hal-audio-sbc.c | 12 ++++++------ + profiles/audio/a2dp-codecs.h | 4 ++-- + 2 files changed, 8 insertions(+), 8 deletions(-) + +diff --git a/android/hal-audio-sbc.c b/android/hal-audio-sbc.c +index 7ad3a6a51..fd6c61b95 100644 +--- a/android/hal-audio-sbc.c ++++ b/android/hal-audio-sbc.c +@@ -90,8 +90,8 @@ static const a2dp_sbc_t sbc_presets[] = { + SBC_ALLOCATION_LOUDNESS, + .block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | + SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16, +- .min_bitpool = MIN_BITPOOL, +- .max_bitpool = MAX_BITPOOL ++ .min_bitpool = SBC_MIN_BITPOOL, ++ .max_bitpool = SBC_MAX_BITPOOL + }, + { + .frequency = SBC_SAMPLING_FREQ_44100, +@@ -99,8 +99,8 @@ static const a2dp_sbc_t sbc_presets[] = { + .subbands = SBC_SUBBANDS_8, + .allocation_method = SBC_ALLOCATION_LOUDNESS, + .block_length = SBC_BLOCK_LENGTH_16, +- .min_bitpool = MIN_BITPOOL, +- .max_bitpool = MAX_BITPOOL ++ .min_bitpool = SBC_MIN_BITPOOL, ++ .max_bitpool = SBC_MAX_BITPOOL + }, + { + .frequency = SBC_SAMPLING_FREQ_48000, +@@ -108,8 +108,8 @@ static const a2dp_sbc_t sbc_presets[] = { + .subbands = SBC_SUBBANDS_8, + .allocation_method = SBC_ALLOCATION_LOUDNESS, + .block_length = SBC_BLOCK_LENGTH_16, +- .min_bitpool = MIN_BITPOOL, +- .max_bitpool = MAX_BITPOOL ++ .min_bitpool = SBC_MIN_BITPOOL, ++ .max_bitpool = SBC_MAX_BITPOOL + }, + }; + +diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h +index 4fb5c0cc9..205491939 100644 +--- a/profiles/audio/a2dp-codecs.h ++++ b/profiles/audio/a2dp-codecs.h +@@ -49,8 +49,8 @@ + #define SBC_ALLOCATION_SNR (1 << 1) + #define SBC_ALLOCATION_LOUDNESS 1 + +-#define MAX_BITPOOL 64 +-#define MIN_BITPOOL 2 ++#define SBC_MAX_BITPOOL 64 ++#define SBC_MIN_BITPOOL 2 + + #define MPEG_CHANNEL_MODE_MONO (1 << 3) + #define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +-- +2.21.0 + + +From b199f360f0c99337544fb6f5479c006f377988db Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:40:15 +0100 +Subject: [PATCH 04/31] a2dp-codecs: Fix codec id for ATRAC + +According to Bluetooth Assigned Numbers for Audio/Video ATRAC has codec id 0x04. +See: https://www.bluetooth.com/specifications/assigned-numbers/audio-video +--- + profiles/audio/a2dp-codecs.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h +index 205491939..25959902c 100644 +--- a/profiles/audio/a2dp-codecs.h ++++ b/profiles/audio/a2dp-codecs.h +@@ -25,7 +25,7 @@ + #define A2DP_CODEC_SBC 0x00 + #define A2DP_CODEC_MPEG12 0x01 + #define A2DP_CODEC_MPEG24 0x02 +-#define A2DP_CODEC_ATRAC 0x03 ++#define A2DP_CODEC_ATRAC 0x04 + #define A2DP_CODEC_VENDOR 0xFF + + #define SBC_SAMPLING_FREQ_16000 (1 << 3) +-- +2.21.0 + + +From dfa516c37d8ab07e4ba0f85289e2fa3246e89f36 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:40:16 +0100 +Subject: [PATCH 05/31] a2dp-codecs & avinfo: Fix parsing MPEG bit rate values + +Redefine bitrate field in a2dp_mpeg_t struct in endian neutral way and +separate vbr field according to A2DP specification. Define new macros +MPEG_GET_BITRATE() and MPEG_SET_BITRATE() for manipulating with bitrate +like for a2dp_aac_t struct. + +And fix meaning of bitrate field. According to A2DP specification, it is +bitrate index, not bitrate itself. According to MPEG specification, each +MPEG layer have different bitrates for bitrate indexes. Therefore define +correctly bitrates for Layers 1, 2 and 3. + +This fixes problems with parsing bitrate field in a2dp_mpeg_t struct as it +was broken due to endianity and it was broken for Layer 1 and 2 as bitrate +definitions was for Layer 3. +--- + profiles/audio/a2dp-codecs.h | 93 +++++++++++++++++++----- + tools/avinfo.c | 135 ++++++++++++++++++++++++++--------- + 2 files changed, 176 insertions(+), 52 deletions(-) + +diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h +index 25959902c..47030bcc1 100644 +--- a/profiles/audio/a2dp-codecs.h ++++ b/profiles/audio/a2dp-codecs.h +@@ -68,22 +68,75 @@ + #define MPEG_SAMPLING_FREQ_44100 (1 << 1) + #define MPEG_SAMPLING_FREQ_48000 1 + +-#define MPEG_BIT_RATE_VBR 0x8000 +-#define MPEG_BIT_RATE_320000 0x4000 +-#define MPEG_BIT_RATE_256000 0x2000 +-#define MPEG_BIT_RATE_224000 0x1000 +-#define MPEG_BIT_RATE_192000 0x0800 +-#define MPEG_BIT_RATE_160000 0x0400 +-#define MPEG_BIT_RATE_128000 0x0200 +-#define MPEG_BIT_RATE_112000 0x0100 +-#define MPEG_BIT_RATE_96000 0x0080 +-#define MPEG_BIT_RATE_80000 0x0040 +-#define MPEG_BIT_RATE_64000 0x0020 +-#define MPEG_BIT_RATE_56000 0x0010 +-#define MPEG_BIT_RATE_48000 0x0008 +-#define MPEG_BIT_RATE_40000 0x0004 +-#define MPEG_BIT_RATE_32000 0x0002 +-#define MPEG_BIT_RATE_FREE 0x0001 ++#define MPEG_BIT_RATE_INDEX_0 (1 << 0) ++#define MPEG_BIT_RATE_INDEX_1 (1 << 1) ++#define MPEG_BIT_RATE_INDEX_2 (1 << 2) ++#define MPEG_BIT_RATE_INDEX_3 (1 << 3) ++#define MPEG_BIT_RATE_INDEX_4 (1 << 4) ++#define MPEG_BIT_RATE_INDEX_5 (1 << 5) ++#define MPEG_BIT_RATE_INDEX_6 (1 << 6) ++#define MPEG_BIT_RATE_INDEX_7 (1 << 7) ++#define MPEG_BIT_RATE_INDEX_8 (1 << 8) ++#define MPEG_BIT_RATE_INDEX_9 (1 << 9) ++#define MPEG_BIT_RATE_INDEX_10 (1 << 10) ++#define MPEG_BIT_RATE_INDEX_11 (1 << 11) ++#define MPEG_BIT_RATE_INDEX_12 (1 << 12) ++#define MPEG_BIT_RATE_INDEX_13 (1 << 13) ++#define MPEG_BIT_RATE_INDEX_14 (1 << 14) ++ ++#define MPEG_MP1_BIT_RATE_32000 MPEG_BIT_RATE_INDEX_1 ++#define MPEG_MP1_BIT_RATE_64000 MPEG_BIT_RATE_INDEX_2 ++#define MPEG_MP1_BIT_RATE_96000 MPEG_BIT_RATE_INDEX_3 ++#define MPEG_MP1_BIT_RATE_128000 MPEG_BIT_RATE_INDEX_4 ++#define MPEG_MP1_BIT_RATE_160000 MPEG_BIT_RATE_INDEX_5 ++#define MPEG_MP1_BIT_RATE_192000 MPEG_BIT_RATE_INDEX_6 ++#define MPEG_MP1_BIT_RATE_224000 MPEG_BIT_RATE_INDEX_7 ++#define MPEG_MP1_BIT_RATE_256000 MPEG_BIT_RATE_INDEX_8 ++#define MPEG_MP1_BIT_RATE_288000 MPEG_BIT_RATE_INDEX_9 ++#define MPEG_MP1_BIT_RATE_320000 MPEG_BIT_RATE_INDEX_10 ++#define MPEG_MP1_BIT_RATE_352000 MPEG_BIT_RATE_INDEX_11 ++#define MPEG_MP1_BIT_RATE_384000 MPEG_BIT_RATE_INDEX_12 ++#define MPEG_MP1_BIT_RATE_416000 MPEG_BIT_RATE_INDEX_13 ++#define MPEG_MP1_BIT_RATE_448000 MPEG_BIT_RATE_INDEX_14 ++ ++#define MPEG_MP2_BIT_RATE_32000 MPEG_BIT_RATE_INDEX_1 ++#define MPEG_MP2_BIT_RATE_48000 MPEG_BIT_RATE_INDEX_2 ++#define MPEG_MP2_BIT_RATE_56000 MPEG_BIT_RATE_INDEX_3 ++#define MPEG_MP2_BIT_RATE_64000 MPEG_BIT_RATE_INDEX_4 ++#define MPEG_MP2_BIT_RATE_80000 MPEG_BIT_RATE_INDEX_5 ++#define MPEG_MP2_BIT_RATE_96000 MPEG_BIT_RATE_INDEX_6 ++#define MPEG_MP2_BIT_RATE_112000 MPEG_BIT_RATE_INDEX_7 ++#define MPEG_MP2_BIT_RATE_128000 MPEG_BIT_RATE_INDEX_8 ++#define MPEG_MP2_BIT_RATE_160000 MPEG_BIT_RATE_INDEX_9 ++#define MPEG_MP2_BIT_RATE_192000 MPEG_BIT_RATE_INDEX_10 ++#define MPEG_MP2_BIT_RATE_224000 MPEG_BIT_RATE_INDEX_11 ++#define MPEG_MP2_BIT_RATE_256000 MPEG_BIT_RATE_INDEX_12 ++#define MPEG_MP2_BIT_RATE_320000 MPEG_BIT_RATE_INDEX_13 ++#define MPEG_MP2_BIT_RATE_384000 MPEG_BIT_RATE_INDEX_14 ++ ++#define MPEG_MP3_BIT_RATE_32000 MPEG_BIT_RATE_INDEX_1 ++#define MPEG_MP3_BIT_RATE_40000 MPEG_BIT_RATE_INDEX_2 ++#define MPEG_MP3_BIT_RATE_48000 MPEG_BIT_RATE_INDEX_3 ++#define MPEG_MP3_BIT_RATE_56000 MPEG_BIT_RATE_INDEX_4 ++#define MPEG_MP3_BIT_RATE_64000 MPEG_BIT_RATE_INDEX_5 ++#define MPEG_MP3_BIT_RATE_80000 MPEG_BIT_RATE_INDEX_6 ++#define MPEG_MP3_BIT_RATE_96000 MPEG_BIT_RATE_INDEX_7 ++#define MPEG_MP3_BIT_RATE_112000 MPEG_BIT_RATE_INDEX_8 ++#define MPEG_MP3_BIT_RATE_128000 MPEG_BIT_RATE_INDEX_9 ++#define MPEG_MP3_BIT_RATE_160000 MPEG_BIT_RATE_INDEX_10 ++#define MPEG_MP3_BIT_RATE_192000 MPEG_BIT_RATE_INDEX_11 ++#define MPEG_MP3_BIT_RATE_224000 MPEG_BIT_RATE_INDEX_12 ++#define MPEG_MP3_BIT_RATE_256000 MPEG_BIT_RATE_INDEX_13 ++#define MPEG_MP3_BIT_RATE_320000 MPEG_BIT_RATE_INDEX_14 ++ ++#define MPEG_BIT_RATE_FREE MPEG_BIT_RATE_INDEX_0 ++ ++#define MPEG_GET_BITRATE(a) ((uint16_t)(a).bitrate1 << 8 | (a).bitrate2) ++#define MPEG_SET_BITRATE(a, b) \ ++ do { \ ++ (a).bitrate1 = ((b) >> 8) & 0x7f; \ ++ (a).bitrate2 = (b) & 0xff; \ ++ } while (0) + + #define AAC_OBJECT_TYPE_MPEG2_AAC_LC 0x80 + #define AAC_OBJECT_TYPE_MPEG4_AAC_LC 0x40 +@@ -168,7 +221,9 @@ typedef struct { + uint8_t frequency:6; + uint8_t mpf:1; + uint8_t rfa:1; +- uint16_t bitrate; ++ uint8_t bitrate1:7; ++ uint8_t vbr:1; ++ uint8_t bitrate2; + } __attribute__ ((packed)) a2dp_mpeg_t; + + typedef struct { +@@ -213,7 +268,9 @@ typedef struct { + uint8_t rfa:1; + uint8_t mpf:1; + uint8_t frequency:6; +- uint16_t bitrate; ++ uint8_t vbr:1; ++ uint8_t bitrate1:7; ++ uint8_t bitrate2; + } __attribute__ ((packed)) a2dp_mpeg_t; + + typedef struct { +diff --git a/tools/avinfo.c b/tools/avinfo.c +index 61bcdab0b..2398cc5e0 100644 +--- a/tools/avinfo.c ++++ b/tools/avinfo.c +@@ -303,11 +303,15 @@ static void print_mpeg24(a2dp_aac_t *aac, uint8_t size) + + static void print_mpeg12(a2dp_mpeg_t *mpeg, uint8_t size) + { ++ uint16_t bitrate; ++ + if (size < sizeof(*mpeg)) { + printf("\tMedia Codec: MPEG12 (broken)\n"); + return; + } + ++ bitrate = MPEG_GET_BITRATE(*mpeg); ++ + printf("\tMedia Codec: MPEG12\n\t\tChannel Modes: "); + + if (mpeg->channel_mode & MPEG_CHANNEL_MODE_MONO) +@@ -343,42 +347,105 @@ static void print_mpeg12(a2dp_mpeg_t *mpeg, uint8_t size) + if (mpeg->layer & MPEG_LAYER_MP3) + printf("3 "); + +- printf("\n\t\tBit Rate: "); +- if (mpeg->bitrate & MPEG_BIT_RATE_FREE) +- printf("Free format"); +- else { +- if (mpeg->bitrate & MPEG_BIT_RATE_32000) +- printf("32kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_40000) +- printf("40kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_48000) +- printf("48kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_56000) +- printf("56kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_64000) +- printf("64kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_80000) +- printf("80kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_96000) +- printf("96kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_112000) +- printf("112kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_128000) +- printf("128kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_160000) +- printf("160kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_192000) +- printf("192kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_224000) +- printf("224kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_256000) +- printf("256kbps "); +- if (mpeg->bitrate & MPEG_BIT_RATE_320000) +- printf("320kbps "); ++ if (bitrate & MPEG_BIT_RATE_FREE) { ++ printf("\n\t\tBit Rate: Free format"); ++ } else { ++ if (mpeg->layer & MPEG_LAYER_MP1) { ++ printf("\n\t\tLayer 1 Bit Rate: "); ++ if (bitrate & MPEG_MP1_BIT_RATE_32000) ++ printf("32kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_64000) ++ printf("64kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_96000) ++ printf("96kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_128000) ++ printf("128kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_160000) ++ printf("160kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_192000) ++ printf("192kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_224000) ++ printf("224kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_256000) ++ printf("256kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_320000) ++ printf("320kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_352000) ++ printf("352kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_384000) ++ printf("384kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_416000) ++ printf("416kbps "); ++ if (bitrate & MPEG_MP1_BIT_RATE_448000) ++ printf("448kbps "); ++ } ++ ++ if (mpeg->layer & MPEG_LAYER_MP2) { ++ printf("\n\t\tLayer 2 Bit Rate: "); ++ if (bitrate & MPEG_MP2_BIT_RATE_32000) ++ printf("32kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_48000) ++ printf("48kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_56000) ++ printf("56kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_64000) ++ printf("64kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_80000) ++ printf("80kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_96000) ++ printf("96kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_112000) ++ printf("112kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_128000) ++ printf("128kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_160000) ++ printf("160kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_192000) ++ printf("192kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_224000) ++ printf("224kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_256000) ++ printf("256kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_320000) ++ printf("320kbps "); ++ if (bitrate & MPEG_MP2_BIT_RATE_384000) ++ printf("384kbps "); ++ } ++ ++ if (mpeg->layer & MPEG_LAYER_MP3) { ++ printf("\n\t\tLayer 3 Bit Rate: "); ++ if (bitrate & MPEG_MP3_BIT_RATE_32000) ++ printf("32kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_40000) ++ printf("40kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_48000) ++ printf("48kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_56000) ++ printf("56kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_64000) ++ printf("64kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_80000) ++ printf("80kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_96000) ++ printf("96kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_112000) ++ printf("112kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_128000) ++ printf("128kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_160000) ++ printf("160kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_192000) ++ printf("192kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_224000) ++ printf("224kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_256000) ++ printf("256kbps "); ++ if (bitrate & MPEG_MP3_BIT_RATE_320000) ++ printf("320kbps "); ++ } + } + +- printf("\n\t\tVBR: %s", mpeg->bitrate & MPEG_BIT_RATE_VBR ? "Yes" : +- "No"); ++ printf("\n\t\tVBR: %s", mpeg->vbr ? "Yes" : "No"); + + printf("\n\t\tPayload Format: "); + if (mpeg->mpf) +-- +2.21.0 + + +From 70874281220368ed24bb99e916b765608c01a44a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:40:17 +0100 +Subject: [PATCH 06/31] a2dp-codecs: Define a2dp_vendor_codec_t struct in + endian neutral way + +And define new macros A2DP_GET_VENDOR_ID(), A2DP_GET_CODEC_ID() and +A2DP_SET_VENDOR_ID_CODEC_ID() for easily filling a2dp_vendor_codec_t +struct. +--- + android/a2dp.c | 8 ++++---- + android/avdtp.c | 6 ++++-- + android/hal-audio-aptx.c | 18 ++++++------------ + profiles/audio/a2dp-codecs.h | 24 ++++++++++++++++++++++-- + profiles/audio/a2dp.c | 9 +++++---- + tools/avinfo.c | 4 ++-- + 6 files changed, 43 insertions(+), 26 deletions(-) + +diff --git a/android/a2dp.c b/android/a2dp.c +index f21904208..8bcdfd20f 100644 +--- a/android/a2dp.c ++++ b/android/a2dp.c +@@ -417,8 +417,8 @@ static int check_capabilities(struct a2dp_preset *preset, + preset->len); + case A2DP_CODEC_VENDOR: + vndcodec = (void *) codec->data; +- if (btohl(vndcodec->vendor_id) == APTX_VENDOR_ID && +- btohs(vndcodec->codec_id) == APTX_CODEC_ID) ++ if (A2DP_GET_VENDOR_ID(*vndcodec) == APTX_VENDOR_ID && ++ A2DP_GET_CODEC_ID(*vndcodec) == APTX_CODEC_ID) + return aptx_check_config(codec->data, codec_len, + preset->data, preset->len); + return -EINVAL; +@@ -1344,8 +1344,8 @@ static uint8_t register_endpoint(const uint8_t *uuid, uint8_t codec, + a2dp_vendor_codec_t *vndcodec = (void *) endpoint->caps->data; + + avdtp_sep_set_vendor_codec(endpoint->sep, +- btohl(vndcodec->vendor_id), +- btohs(vndcodec->codec_id)); ++ A2DP_GET_VENDOR_ID(*vndcodec), ++ A2DP_GET_CODEC_ID(*vndcodec)); + } + + endpoints = g_slist_append(endpoints, endpoint); +diff --git a/android/avdtp.c b/android/avdtp.c +index 34caf3db5..7fb8cb731 100644 +--- a/android/avdtp.c ++++ b/android/avdtp.c +@@ -1103,10 +1103,12 @@ struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session, + a2dp_vendor_codec_t *vndcodec = + (void *) codec_data->data; + +- if (btohl(vndcodec->vendor_id) != lsep->vndcodec_vendor) ++ if (A2DP_GET_VENDOR_ID(*vndcodec) != ++ lsep->vndcodec_vendor) + continue; + +- if (btohs(vndcodec->codec_id) != lsep->vndcodec_codec) ++ if (A2DP_GET_CODEC_ID(*vndcodec) != ++ lsep->vndcodec_codec) + continue; + } + +diff --git a/android/hal-audio-aptx.c b/android/hal-audio-aptx.c +index a8000759b..4707f593e 100644 +--- a/android/hal-audio-aptx.c ++++ b/android/hal-audio-aptx.c +@@ -36,27 +36,21 @@ struct aptx_data { + + static const a2dp_aptx_t aptx_presets[] = { + { +- .info = { +- .vendor_id = APTX_VENDOR_ID, +- .codec_id = APTX_CODEC_ID, +- }, ++ .info = ++ A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID), + .frequency = APTX_SAMPLING_FREQ_44100 | + APTX_SAMPLING_FREQ_48000, + .channel_mode = APTX_CHANNEL_MODE_STEREO, + }, + { +- .info = { +- .vendor_id = APTX_VENDOR_ID, +- .codec_id = APTX_CODEC_ID, +- }, ++ .info = ++ A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID), + .frequency = APTX_SAMPLING_FREQ_48000, + .channel_mode = APTX_CHANNEL_MODE_STEREO, + }, + { +- .info = { +- .vendor_id = APTX_VENDOR_ID, +- .codec_id = APTX_CODEC_ID, +- }, ++ .info = ++ A2DP_SET_VENDOR_ID_CODEC_ID(APTX_VENDOR_ID, APTX_CODEC_ID), + .frequency = APTX_SAMPLING_FREQ_44100, + .channel_mode = APTX_CHANNEL_MODE_STEREO, + }, +diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h +index 47030bcc1..a310efe49 100644 +--- a/profiles/audio/a2dp-codecs.h ++++ b/profiles/audio/a2dp-codecs.h +@@ -198,10 +198,30 @@ + #define LDAC_CODEC_ID 0x00aa + + typedef struct { +- uint32_t vendor_id; +- uint16_t codec_id; ++ uint8_t vendor_id4; ++ uint8_t vendor_id3; ++ uint8_t vendor_id2; ++ uint8_t vendor_id1; ++ uint8_t codec_id2; ++ uint8_t codec_id1; + } __attribute__ ((packed)) a2dp_vendor_codec_t; + ++#define A2DP_GET_VENDOR_ID(a) ( \ ++ (((uint32_t)(a).vendor_id4) << 0) | \ ++ (((uint32_t)(a).vendor_id3) << 8) | \ ++ (((uint32_t)(a).vendor_id2) << 16) | \ ++ (((uint32_t)(a).vendor_id1) << 24) \ ++ ) ++#define A2DP_GET_CODEC_ID(a) ((a).codec_id2 | (((uint16_t)(a).codec_id1) << 8)) ++#define A2DP_SET_VENDOR_ID_CODEC_ID(v, c) ((a2dp_vendor_codec_t){ \ ++ .vendor_id4 = (((v) >> 0) & 0xff), \ ++ .vendor_id3 = (((v) >> 8) & 0xff), \ ++ .vendor_id2 = (((v) >> 16) & 0xff), \ ++ .vendor_id1 = (((v) >> 24) & 0xff), \ ++ .codec_id2 = (((c) >> 0) & 0xff), \ ++ .codec_id1 = (((c) >> 8) & 0xff), \ ++ }) ++ + #if __BYTE_ORDER == __LITTLE_ENDIAN + + typedef struct { +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index fc98bb264..344459332 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -523,14 +523,15 @@ static gboolean endpoint_match_codec_ind(struct avdtp *session, + local_codec = (a2dp_vendor_codec_t *) capabilities; + remote_codec = (a2dp_vendor_codec_t *) codec->data; + +- if (remote_codec->vendor_id != local_codec->vendor_id) ++ if (A2DP_GET_VENDOR_ID(*remote_codec) != ++ A2DP_GET_VENDOR_ID(*local_codec)) + return FALSE; + +- if (remote_codec->codec_id != local_codec->codec_id) ++ if (A2DP_GET_CODEC_ID(*remote_codec) != A2DP_GET_CODEC_ID(*local_codec)) + return FALSE; + +- DBG("vendor 0x%08x codec 0x%04x", btohl(remote_codec->vendor_id), +- btohs(remote_codec->codec_id)); ++ DBG("vendor 0x%08x codec 0x%04x", A2DP_GET_VENDOR_ID(*remote_codec), ++ A2DP_GET_CODEC_ID(*remote_codec)); + return TRUE; + } + +diff --git a/tools/avinfo.c b/tools/avinfo.c +index 2398cc5e0..424221f8d 100644 +--- a/tools/avinfo.c ++++ b/tools/avinfo.c +@@ -221,8 +221,8 @@ static void print_vendor(a2dp_vendor_codec_t *vendor, uint8_t size) + return; + } + +- vendor_id = btohl(vendor->vendor_id); +- codec_id = btohs(vendor->codec_id); ++ vendor_id = A2DP_GET_VENDOR_ID(*vendor); ++ codec_id = A2DP_GET_CODEC_ID(*vendor); + + printf("\tMedia Codec: Vendor Specific A2DP Codec"); + +-- +2.21.0 + + +From f20550833ec0a3fee5930cc25db0b51f8492f65a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:40:18 +0100 +Subject: [PATCH 07/31] a2dp-codecs: Add needed includes and properly check for + endian macros + +Without stdint.h type uint8_t cannot be used. + +And without endian.h macros __BYTE_ORDER, __LITTLE_ENDIAN and __BIG_ENDIAN +are not defined. + +When both __BYTE_ORDER and __LITTLE_ENDIAN macros are not defined, then +condition #if __BYTE_ORDER == __LITTLE_ENDIAN is true. +--- + profiles/audio/a2dp-codecs.h | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h +index a310efe49..649e2411b 100644 +--- a/profiles/audio/a2dp-codecs.h ++++ b/profiles/audio/a2dp-codecs.h +@@ -22,6 +22,9 @@ + * + */ + ++#include ++#include ++ + #define A2DP_CODEC_SBC 0x00 + #define A2DP_CODEC_MPEG12 0x01 + #define A2DP_CODEC_MPEG24 0x02 +@@ -222,7 +225,8 @@ typedef struct { + .codec_id1 = (((c) >> 8) & 0xff), \ + }) + +-#if __BYTE_ORDER == __LITTLE_ENDIAN ++#if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ ++ __BYTE_ORDER == __LITTLE_ENDIAN + + typedef struct { + uint8_t channel_mode:4; +@@ -269,7 +273,8 @@ typedef struct { + uint8_t unknown[2]; + } __attribute__ ((packed)) a2dp_ldac_t; + +-#elif __BYTE_ORDER == __BIG_ENDIAN ++#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ ++ __BYTE_ORDER == __BIG_ENDIAN + + typedef struct { + uint8_t frequency:4; +-- +2.21.0 + + +From 99c71523e1d4b33885d2e095f3a7539764edbce8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:40:19 +0100 +Subject: [PATCH 08/31] a2dp-codecs: Properly define macros and struct for LDAC + codec + +--- + profiles/audio/a2dp-codecs.h | 27 +++++++++++++++++---------- + tools/avinfo.c | 4 ++-- + 2 files changed, 19 insertions(+), 12 deletions(-) + +diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h +index 649e2411b..6f667d3aa 100644 +--- a/profiles/audio/a2dp-codecs.h ++++ b/profiles/audio/a2dp-codecs.h +@@ -200,6 +200,17 @@ + #define LDAC_VENDOR_ID 0x0000012d + #define LDAC_CODEC_ID 0x00aa + ++#define LDAC_SAMPLING_FREQ_44100 0x20 ++#define LDAC_SAMPLING_FREQ_48000 0x10 ++#define LDAC_SAMPLING_FREQ_88200 0x08 ++#define LDAC_SAMPLING_FREQ_96000 0x04 ++#define LDAC_SAMPLING_FREQ_176400 0x02 ++#define LDAC_SAMPLING_FREQ_192000 0x01 ++ ++#define LDAC_CHANNEL_MODE_MONO 0x04 ++#define LDAC_CHANNEL_MODE_DUAL 0x02 ++#define LDAC_CHANNEL_MODE_STEREO 0x01 ++ + typedef struct { + uint8_t vendor_id4; + uint8_t vendor_id3; +@@ -225,6 +236,12 @@ typedef struct { + .codec_id1 = (((c) >> 8) & 0xff), \ + }) + ++typedef struct { ++ a2dp_vendor_codec_t info; ++ uint8_t frequency; ++ uint8_t channel_mode; ++} __attribute__ ((packed)) a2dp_ldac_t; ++ + #if defined(__BYTE_ORDER) && defined(__LITTLE_ENDIAN) && \ + __BYTE_ORDER == __LITTLE_ENDIAN + +@@ -268,11 +285,6 @@ typedef struct { + uint8_t frequency:4; + } __attribute__ ((packed)) a2dp_aptx_t; + +-typedef struct { +- a2dp_vendor_codec_t info; +- uint8_t unknown[2]; +-} __attribute__ ((packed)) a2dp_ldac_t; +- + #elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN + +@@ -316,11 +328,6 @@ typedef struct { + uint8_t channel_mode:4; + } __attribute__ ((packed)) a2dp_aptx_t; + +-typedef struct { +- a2dp_vendor_codec_t info; +- uint8_t unknown[2]; +-} __attribute__ ((packed)) a2dp_ldac_t; +- + #else + #error "Unknown byte order" + #endif +diff --git a/tools/avinfo.c b/tools/avinfo.c +index 424221f8d..1852b2473 100644 +--- a/tools/avinfo.c ++++ b/tools/avinfo.c +@@ -204,8 +204,8 @@ static void print_ldac(a2dp_ldac_t *ldac, uint8_t size) + return; + } + +- printf("\n\t\t\tUnknown: %02x %02x", ldac->unknown[0], +- ldac->unknown[1]); ++ printf("\n\t\t\tUnknown: %02x %02x", ldac->frequency, ++ ldac->channel_mode); + + printf("\n"); + } +-- +2.21.0 + + +From 8e119a083b931a53d19ba218973c31bdda56138d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:40:20 +0100 +Subject: [PATCH 09/31] a2dp-codecs: Add macros and structures for new codecs + +Add additional codecs: FastStream, aptX Low Latency and aptX HD codecs. +--- + profiles/audio/a2dp-codecs.h | 120 +++++++++++++++++++++++++++++++++++ + 1 file changed, 120 insertions(+) + +diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h +index 6f667d3aa..0bdd29110 100644 +--- a/profiles/audio/a2dp-codecs.h ++++ b/profiles/audio/a2dp-codecs.h +@@ -4,6 +4,7 @@ + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann ++ * Copyright (C) 2018 Pali Rohár + * + * + * This library is free software; you can redistribute it and/or +@@ -197,6 +198,59 @@ + #define APTX_SAMPLING_FREQ_44100 0x02 + #define APTX_SAMPLING_FREQ_48000 0x01 + ++#define FASTSTREAM_VENDOR_ID 0x0000000a ++#define FASTSTREAM_CODEC_ID 0x0001 ++ ++#define FASTSTREAM_DIRECTION_SINK 0x1 ++#define FASTSTREAM_DIRECTION_SOURCE 0x2 ++ ++#define FASTSTREAM_SINK_SAMPLING_FREQ_44100 0x2 ++#define FASTSTREAM_SINK_SAMPLING_FREQ_48000 0x1 ++ ++#define FASTSTREAM_SOURCE_SAMPLING_FREQ_16000 0x2 ++ ++#define APTX_LL_VENDOR_ID 0x0000000a ++#define APTX_LL_CODEC_ID 0x0002 ++ ++#define APTX_LL_CHANNEL_MODE_MONO 0x01 ++#define APTX_LL_CHANNEL_MODE_STEREO 0x02 ++ ++#define APTX_LL_SAMPLING_FREQ_16000 0x08 ++#define APTX_LL_SAMPLING_FREQ_32000 0x04 ++#define APTX_LL_SAMPLING_FREQ_44100 0x02 ++#define APTX_LL_SAMPLING_FREQ_48000 0x01 ++ ++/* Default parameters for aptX Low Latency encoder */ ++ ++/* Target codec buffer level = 180 */ ++#define APTX_LL_TARGET_LEVEL2 0xb4 ++#define APTX_LL_TARGET_LEVEL1 0x00 ++ ++/* Initial codec buffer level = 360 */ ++#define APTX_LL_INITIAL_LEVEL2 0x68 ++#define APTX_LL_INITIAL_LEVEL1 0x01 ++ ++/* SRA max rate 0.005 * 10000 = 50 */ ++#define APTX_LL_SRA_MAX_RATE 0x32 ++ ++/* SRA averaging time = 1s */ ++#define APTX_LL_SRA_AVG_TIME 0x01 ++ ++/* Good working codec buffer level = 180 */ ++#define APTX_LL_GOOD_WORKING_LEVEL2 0xB4 ++#define APTX_LL_GOOD_WORKING_LEVEL1 0x00 ++ ++#define APTX_HD_VENDOR_ID 0x000000D7 ++#define APTX_HD_CODEC_ID 0x0024 ++ ++#define APTX_HD_CHANNEL_MODE_MONO 0x1 ++#define APTX_HD_CHANNEL_MODE_STEREO 0x2 ++ ++#define APTX_HD_SAMPLING_FREQ_16000 0x8 ++#define APTX_HD_SAMPLING_FREQ_32000 0x4 ++#define APTX_HD_SAMPLING_FREQ_44100 0x2 ++#define APTX_HD_SAMPLING_FREQ_48000 0x1 ++ + #define LDAC_VENDOR_ID 0x0000012d + #define LDAC_CODEC_ID 0x00aa + +@@ -236,6 +290,18 @@ typedef struct { + .codec_id1 = (((c) >> 8) & 0xff), \ + }) + ++typedef struct { ++ uint8_t reserved; ++ uint8_t target_level2; ++ uint8_t target_level1; ++ uint8_t initial_level2; ++ uint8_t initial_level1; ++ uint8_t sra_max_rate; ++ uint8_t sra_avg_time; ++ uint8_t good_working_level2; ++ uint8_t good_working_level1; ++} __attribute__ ((packed)) a2dp_aptx_ll_new_caps_t; ++ + typedef struct { + a2dp_vendor_codec_t info; + uint8_t frequency; +@@ -285,6 +351,33 @@ typedef struct { + uint8_t frequency:4; + } __attribute__ ((packed)) a2dp_aptx_t; + ++typedef struct { ++ a2dp_vendor_codec_t info; ++ uint8_t direction; ++ uint8_t sink_frequency:4; ++ uint8_t source_frequency:4; ++} __attribute__ ((packed)) a2dp_faststream_t; ++ ++typedef struct { ++ a2dp_vendor_codec_t info; ++ uint8_t channel_mode:4; ++ uint8_t frequency:4; ++ uint8_t bidirect_link:1; ++ uint8_t has_new_caps:1; ++ uint8_t reserved:6; ++ a2dp_aptx_ll_new_caps_t new_caps[0]; ++} __attribute__ ((packed)) a2dp_aptx_ll_t; ++ ++typedef struct { ++ a2dp_vendor_codec_t info; ++ uint8_t channel_mode:4; ++ uint8_t frequency:4; ++ uint8_t reserved0; ++ uint8_t reserved1; ++ uint8_t reserved2; ++ uint8_t reserved3; ++} __attribute__ ((packed)) a2dp_aptx_hd_t; ++ + #elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN + +@@ -328,6 +421,33 @@ typedef struct { + uint8_t channel_mode:4; + } __attribute__ ((packed)) a2dp_aptx_t; + ++typedef struct { ++ a2dp_vendor_codec_t info; ++ uint8_t direction; ++ uint8_t source_frequency:4; ++ uint8_t sink_frequency:4; ++} __attribute__ ((packed)) a2dp_faststream_t; ++ ++typedef struct { ++ a2dp_vendor_codec_t info; ++ uint8_t frequency:4; ++ uint8_t channel_mode:4; ++ uint8_t reserved:6; ++ uint8_t has_new_caps:1; ++ uint8_t bidirect_link:1; ++ a2dp_aptx_ll_new_caps_t new_caps[0]; ++} __attribute__ ((packed)) a2dp_aptx_ll_t; ++ ++typedef struct { ++ a2dp_vendor_codec_t info; ++ uint8_t frequency:4; ++ uint8_t channel_mode:4; ++ uint8_t reserved0; ++ uint8_t reserved1; ++ uint8_t reserved2; ++ uint8_t reserved3; ++} __attribute__ ((packed)) a2dp_aptx_hd_t; ++ + #else + #error "Unknown byte order" + #endif +-- +2.21.0 + + +From 7a2ea44b4c9929507a679debf0cf74416146a5d0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:40:21 +0100 +Subject: [PATCH 10/31] avinfo: Parse new A2DP codecs + +Parse information about additional A2DP codecs: FastStream, aptX Low +Latency, aptX HD and LDAC. +--- + tools/avinfo.c | 146 ++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 144 insertions(+), 2 deletions(-) + +diff --git a/tools/avinfo.c b/tools/avinfo.c +index 1852b2473..02fc1f233 100644 +--- a/tools/avinfo.c ++++ b/tools/avinfo.c +@@ -4,6 +4,7 @@ + * + * Copyright (C) 2006-2010 Nokia Corporation + * Copyright (C) 2004-2010 Marcel Holtmann ++ * Copyright (C) 2018 Pali Rohár + * + * + * This program is free software; you can redistribute it and/or modify +@@ -195,6 +196,121 @@ static void print_aptx(a2dp_aptx_t *aptx, uint8_t size) + printf("\n"); + } + ++static void print_faststream(a2dp_faststream_t *faststream, uint8_t size) ++{ ++ printf("\t\tVendor Specific Value (FastStream)"); ++ ++ if (size < sizeof(*faststream)) { ++ printf(" (broken)\n"); ++ return; ++ } ++ ++ printf("\n\t\t\tDirections: "); ++ if (faststream->direction & FASTSTREAM_DIRECTION_SINK) ++ printf("sink "); ++ if (faststream->direction & FASTSTREAM_DIRECTION_SOURCE) ++ printf("source "); ++ ++ if (faststream->direction & FASTSTREAM_DIRECTION_SINK) { ++ printf("\n\t\t\tSink Frequencies: "); ++ if (faststream->sink_frequency & ++ FASTSTREAM_SINK_SAMPLING_FREQ_44100) ++ printf("44.1kHz "); ++ if (faststream->sink_frequency & ++ FASTSTREAM_SINK_SAMPLING_FREQ_48000) ++ printf("48kHz "); ++ } ++ ++ if (faststream->direction & FASTSTREAM_DIRECTION_SOURCE) { ++ printf("\n\t\t\tSource Frequencies: "); ++ if (faststream->source_frequency & ++ FASTSTREAM_SOURCE_SAMPLING_FREQ_16000) ++ printf("16kHz "); ++ } ++ ++ printf("\n"); ++} ++ ++static void print_aptx_ll(a2dp_aptx_ll_t *aptx_ll, uint8_t size) ++{ ++ a2dp_aptx_ll_new_caps_t *aptx_ll_new; ++ ++ printf("\t\tVendor Specific Value (aptX Low Latency)"); ++ ++ if (size < sizeof(*aptx_ll)) { ++ printf(" (broken)\n"); ++ return; ++ } ++ ++ printf("\n\t\t\tFrequencies: "); ++ if (aptx_ll->frequency & APTX_LL_SAMPLING_FREQ_16000) ++ printf("16kHz "); ++ if (aptx_ll->frequency & APTX_LL_SAMPLING_FREQ_32000) ++ printf("32kHz "); ++ if (aptx_ll->frequency & APTX_LL_SAMPLING_FREQ_44100) ++ printf("44.1kHz "); ++ if (aptx_ll->frequency & APTX_LL_SAMPLING_FREQ_48000) ++ printf("48kHz "); ++ ++ printf("\n\t\t\tChannel modes: "); ++ if (aptx_ll->channel_mode & APTX_LL_CHANNEL_MODE_MONO) ++ printf("Mono "); ++ if (aptx_ll->channel_mode & APTX_LL_CHANNEL_MODE_STEREO) ++ printf("Stereo "); ++ ++ printf("\n\t\tBidirectional link: %s", ++ aptx_ll->bidirect_link ? "Yes" : "No"); ++ ++ aptx_ll_new = &aptx_ll->new_caps[0]; ++ if (aptx_ll->has_new_caps && ++ size >= sizeof(*aptx_ll) + sizeof(*aptx_ll_new)) { ++ printf("\n\t\tTarget codec buffer level: %u", ++ (unsigned int)aptx_ll_new->target_level2 | ++ ((unsigned int)(aptx_ll_new->target_level1) << 8)); ++ printf("\n\t\tInitial codec buffer level: %u", ++ (unsigned int)aptx_ll_new->initial_level2 | ++ ((unsigned int)(aptx_ll_new->initial_level1) << 8)); ++ printf("\n\t\tSRA max rate: %g", ++ aptx_ll_new->sra_max_rate / 10000.0); ++ printf("\n\t\tSRA averaging time: %us", ++ (unsigned int)aptx_ll_new->sra_avg_time); ++ printf("\n\t\tGood working codec buffer level: %u", ++ (unsigned int)aptx_ll_new->good_working_level2 | ++ ((unsigned int)(aptx_ll_new->good_working_level1) << 8) ++ ); ++ } ++ ++ printf("\n"); ++} ++ ++static void print_aptx_hd(a2dp_aptx_hd_t *aptx_hd, uint8_t size) ++{ ++ printf("\t\tVendor Specific Value (aptX HD)"); ++ ++ if (size < sizeof(*aptx_hd)) { ++ printf(" (broken)\n"); ++ return; ++ } ++ ++ printf("\n\t\t\tFrequencies: "); ++ if (aptx_hd->frequency & APTX_HD_SAMPLING_FREQ_16000) ++ printf("16kHz "); ++ if (aptx_hd->frequency & APTX_HD_SAMPLING_FREQ_32000) ++ printf("32kHz "); ++ if (aptx_hd->frequency & APTX_HD_SAMPLING_FREQ_44100) ++ printf("44.1kHz "); ++ if (aptx_hd->frequency & APTX_HD_SAMPLING_FREQ_48000) ++ printf("48kHz "); ++ ++ printf("\n\t\t\tChannel modes: "); ++ if (aptx_hd->channel_mode & APTX_HD_CHANNEL_MODE_MONO) ++ printf("Mono "); ++ if (aptx_hd->channel_mode & APTX_HD_CHANNEL_MODE_STEREO) ++ printf("Stereo "); ++ ++ printf("\n"); ++} ++ + static void print_ldac(a2dp_ldac_t *ldac, uint8_t size) + { + printf("\t\tVendor Specific Value (LDAC)"); +@@ -204,8 +320,27 @@ static void print_ldac(a2dp_ldac_t *ldac, uint8_t size) + return; + } + +- printf("\n\t\t\tUnknown: %02x %02x", ldac->frequency, +- ldac->channel_mode); ++ printf("\n\t\t\tFrequencies: "); ++ if (ldac->frequency & LDAC_SAMPLING_FREQ_44100) ++ printf("44.1kHz "); ++ if (ldac->frequency & LDAC_SAMPLING_FREQ_48000) ++ printf("48kHz "); ++ if (ldac->frequency & LDAC_SAMPLING_FREQ_88200) ++ printf("88.2kHz "); ++ if (ldac->frequency & LDAC_SAMPLING_FREQ_96000) ++ printf("96kHz "); ++ if (ldac->frequency & LDAC_SAMPLING_FREQ_176400) ++ printf("176.4kHz "); ++ if (ldac->frequency & LDAC_SAMPLING_FREQ_192000) ++ printf("192kHz "); ++ ++ printf("\n\t\t\tChannel modes: "); ++ if (ldac->channel_mode & LDAC_CHANNEL_MODE_MONO) ++ printf("Mono "); ++ if (ldac->channel_mode & LDAC_CHANNEL_MODE_DUAL) ++ printf("Dual "); ++ if (ldac->channel_mode & LDAC_CHANNEL_MODE_STEREO) ++ printf("Stereo "); + + printf("\n"); + } +@@ -237,6 +372,13 @@ static void print_vendor(a2dp_vendor_codec_t *vendor, uint8_t size) + + if (vendor_id == APTX_VENDOR_ID && codec_id == APTX_CODEC_ID) + print_aptx((void *) vendor, size); ++ else if (vendor_id == FASTSTREAM_VENDOR_ID && ++ codec_id == FASTSTREAM_CODEC_ID) ++ print_faststream((void *) vendor, size); ++ else if (vendor_id == APTX_LL_VENDOR_ID && codec_id == APTX_LL_CODEC_ID) ++ print_aptx_ll((void *) vendor, size); ++ else if (vendor_id == APTX_HD_VENDOR_ID && codec_id == APTX_HD_CODEC_ID) ++ print_aptx_hd((void *) vendor, size); + else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID) + print_ldac((void *) vendor, size); + } +-- +2.21.0 + + +From 61bc87a7702f0ed544d190444938ebece135547d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sun, 23 Dec 2018 11:00:43 +0100 +Subject: [PATCH 11/31] btmon: Parse new A2DP codecs + +Parse information about additional A2DP codecs: FastStream, aptX Low +Latency and aptX HD. +--- + monitor/a2dp.c | 296 +++++++++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 284 insertions(+), 12 deletions(-) + +diff --git a/monitor/a2dp.c b/monitor/a2dp.c +index 94f9758aa..3e798a7ee 100644 +--- a/monitor/a2dp.c ++++ b/monitor/a2dp.c +@@ -3,6 +3,7 @@ + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2015 Andrzej Kaczmarek ++ * Copyright (C) 2018 Pali Rohár + * + * + * This library is free software; you can redistribute it and/or +@@ -50,6 +51,12 @@ + /* Vendor Specific A2DP Codecs */ + #define APTX_VENDOR_ID 0x0000004f + #define APTX_CODEC_ID 0x0001 ++#define FASTSTREAM_VENDOR_ID 0x0000000a ++#define FASTSTREAM_CODEC_ID 0x0001 ++#define APTX_LL_VENDOR_ID 0x0000000a ++#define APTX_LL_CODEC_ID 0x0002 ++#define APTX_HD_VENDOR_ID 0x000000D7 ++#define APTX_HD_CODEC_ID 0x0024 + #define LDAC_VENDOR_ID 0x0000012d + #define LDAC_CODEC_ID 0x00aa + +@@ -186,6 +193,23 @@ static const struct bit_desc aptx_channel_mode_table[] = { + { } + }; + ++static const struct bit_desc faststream_direction_table[] = { ++ { 0, "Sink" }, ++ { 1, "Source" }, ++ { } ++}; ++ ++static const struct bit_desc faststream_sink_frequency_table[] = { ++ { 1, "44100" }, ++ { 0, "48000" }, ++ { } ++}; ++ ++static const struct bit_desc faststream_source_frequency_table[] = { ++ { 5, "16000" }, ++ { } ++}; ++ + static void print_value_bits(uint8_t indent, uint32_t value, + const struct bit_desc *table) + { +@@ -210,12 +234,49 @@ static const char *find_value_bit(uint32_t value, + return "Unknown"; + } + ++struct vndcodec { ++ uint32_t vendor_id; ++ uint16_t codec_id; ++ char *codec_name; ++ bool (*codec_vendor_cap)(uint8_t losc, struct l2cap_frame *frame); ++ bool (*codec_vendor_cfg)(uint8_t losc, struct l2cap_frame *frame); ++}; ++ ++static bool codec_vendor_aptx_cap(uint8_t losc, struct l2cap_frame *frame); ++static bool codec_vendor_aptx_cfg(uint8_t losc, struct l2cap_frame *frame); ++static bool codec_vendor_faststream_cap(uint8_t losc, ++ struct l2cap_frame *frame); ++static bool codec_vendor_faststream_cfg(uint8_t losc, ++ struct l2cap_frame *frame); ++static bool codec_vendor_aptx_ll_cap(uint8_t losc, struct l2cap_frame *frame); ++static bool codec_vendor_aptx_ll_cfg(uint8_t losc, struct l2cap_frame *frame); ++static bool codec_vendor_aptx_hd_cap(uint8_t losc, struct l2cap_frame *frame); ++static bool codec_vendor_aptx_hd_cfg(uint8_t losc, struct l2cap_frame *frame); ++static bool codec_vendor_ldac(uint8_t losc, struct l2cap_frame *frame); ++ ++static const struct vndcodec vndcodecs[] = { ++ { APTX_VENDOR_ID, APTX_CODEC_ID, "aptX", ++ codec_vendor_aptx_cap, codec_vendor_aptx_cfg }, ++ { FASTSTREAM_VENDOR_ID, FASTSTREAM_CODEC_ID, "FastStream", ++ codec_vendor_faststream_cap, codec_vendor_faststream_cfg }, ++ { APTX_LL_VENDOR_ID, APTX_LL_CODEC_ID, "aptX Low Latency", ++ codec_vendor_aptx_ll_cap, codec_vendor_aptx_ll_cfg }, ++ { APTX_HD_VENDOR_ID, APTX_HD_CODEC_ID, "aptX HD", ++ codec_vendor_aptx_hd_cap, codec_vendor_aptx_hd_cfg }, ++ { LDAC_VENDOR_ID, LDAC_CODEC_ID, "LDAC", ++ codec_vendor_ldac, codec_vendor_ldac }, ++ { } ++}; ++ + 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"; ++ size_t i; ++ ++ for (i = 0; i < sizeof(vndcodecs)/sizeof(*vndcodecs); i++) { ++ if (vndcodecs[i].vendor_id == vendor_id && ++ vndcodecs[i].codec_id == codec_id) ++ return vndcodecs[i].codec_name; ++ } + + return "Unknown"; + } +@@ -507,6 +568,108 @@ static bool codec_vendor_aptx_cap(uint8_t losc, struct l2cap_frame *frame) + return true; + } + ++static bool codec_vendor_faststream_cap(uint8_t losc, struct l2cap_frame *frame) ++{ ++ uint8_t cap = 0; ++ ++ if (losc != 2) ++ return false; ++ ++ l2cap_frame_get_u8(frame, &cap); ++ ++ print_field("%*cDirection: 0x%02x", BASE_INDENT + 2, ' ', cap); ++ print_value_bits(BASE_INDENT + 2, cap, faststream_direction_table); ++ ++ l2cap_frame_get_u8(frame, &cap); ++ ++ print_field("%*cSink Frequency: 0x%02x", BASE_INDENT + 2, ' ', ++ cap & 0x0f); ++ print_value_bits(BASE_INDENT + 2, cap & 0x0f, ++ faststream_sink_frequency_table); ++ ++ print_field("%*cSource Frequency: 0x%02x", BASE_INDENT + 2, ' ', ++ cap & 0xf0); ++ print_value_bits(BASE_INDENT + 2, cap & 0xf0, ++ faststream_source_frequency_table); ++ ++ return true; ++} ++ ++static bool codec_vendor_aptx_ll_cap(uint8_t losc, struct l2cap_frame *frame) ++{ ++ uint8_t cap = 0; ++ uint16_t level = 0; ++ ++ if (losc != 2 && losc != 11) ++ 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); ++ ++ l2cap_frame_get_u8(frame, &cap); ++ ++ print_field("%*cBidirectional link: %s", BASE_INDENT, ' ', ++ (cap & 1) ? "Yes" : "No"); ++ ++ if ((cap & 2) && losc == 11) { ++ /* reserved */ ++ l2cap_frame_get_u8(frame, &cap); ++ ++ l2cap_frame_get_le16(frame, &level); ++ print_field("%*cTarget codec buffer level: %u (0x%02x)", ++ BASE_INDENT + 2, ' ', level, level); ++ ++ l2cap_frame_get_le16(frame, &level); ++ print_field("%*cInitial codec buffer level: %u (0x%02x)", ++ BASE_INDENT + 2, ' ', level, level); ++ ++ l2cap_frame_get_u8(frame, &cap); ++ print_field("%*cSRA max rate: %g (0x%02x)", ++ BASE_INDENT + 2, ' ', cap / 10000.0, cap); ++ ++ l2cap_frame_get_u8(frame, &cap); ++ print_field("%*cSRA averaging time: %us (0x%02x)", ++ BASE_INDENT + 2, ' ', cap, cap); ++ ++ l2cap_frame_get_le16(frame, &level); ++ print_field("%*cGood working codec buffer level: %u (0x%02x)", ++ BASE_INDENT + 2, ' ', level, level); ++ } ++ ++ return true; ++} ++ ++static bool codec_vendor_aptx_hd_cap(uint8_t losc, struct l2cap_frame *frame) ++{ ++ uint8_t cap = 0; ++ ++ if (losc != 5) ++ 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); ++ ++ /* reserved */ ++ l2cap_frame_get_u8(frame, &cap); ++ l2cap_frame_get_u8(frame, &cap); ++ l2cap_frame_get_u8(frame, &cap); ++ l2cap_frame_get_u8(frame, &cap); ++ ++ return true; ++} ++ + static bool codec_vendor_ldac(uint8_t losc, struct l2cap_frame *frame) + { + uint16_t cap = 0; +@@ -525,6 +688,7 @@ static bool codec_vendor_cap(uint8_t losc, struct l2cap_frame *frame) + { + uint32_t vendor_id = 0; + uint16_t codec_id = 0; ++ size_t i; + + if (losc < 6) + return false; +@@ -540,10 +704,11 @@ static bool codec_vendor_cap(uint8_t losc, struct l2cap_frame *frame) + 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) +- return codec_vendor_aptx_cap(losc, frame); +- else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID) +- return codec_vendor_ldac(losc, frame); ++ for (i = 0; i < sizeof(vndcodecs)/sizeof(*vndcodecs); i++) { ++ if (vndcodecs[i].vendor_id == vendor_id && ++ vndcodecs[i].codec_id == codec_id) ++ return vndcodecs[i].codec_vendor_cap(losc, frame); ++ } + + packet_hexdump(frame->data, losc); + l2cap_frame_pull(frame, frame, losc); +@@ -571,10 +736,116 @@ static bool codec_vendor_aptx_cfg(uint8_t losc, struct l2cap_frame *frame) + return true; + } + ++static bool codec_vendor_faststream_cfg(uint8_t losc, struct l2cap_frame *frame) ++{ ++ uint8_t cap = 0; ++ ++ if (losc != 2) ++ return false; ++ ++ l2cap_frame_get_u8(frame, &cap); ++ ++ print_field("%*cDirection: %s (0x%02x)", BASE_INDENT + 2, ' ', ++ find_value_bit(cap, faststream_direction_table), ++ cap); ++ ++ l2cap_frame_get_u8(frame, &cap); ++ ++ print_field("%*cSink Frequency: %s (0x%02x)", BASE_INDENT + 2, ' ', ++ find_value_bit(cap & 0x0f, ++ faststream_sink_frequency_table), ++ cap & 0x0f); ++ ++ print_field("%*cSource Frequency: %s (0x%02x)", BASE_INDENT + 2, ' ', ++ find_value_bit(cap & 0xf0, ++ faststream_source_frequency_table), ++ cap & 0xf0); ++ ++ return true; ++} ++ ++static bool codec_vendor_aptx_ll_cfg(uint8_t losc, struct l2cap_frame *frame) ++{ ++ uint8_t cap = 0; ++ uint16_t level = 0; ++ ++ if (losc != 2 && losc != 11) ++ 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); ++ ++ l2cap_frame_get_u8(frame, &cap); ++ ++ print_field("%*cBidirectional link: %s", BASE_INDENT, ' ', ++ (cap & 1) ? "Yes" : "No"); ++ ++ if ((cap & 2) && losc == 11) { ++ /* reserved */ ++ l2cap_frame_get_u8(frame, &cap); ++ ++ l2cap_frame_get_le16(frame, &level); ++ print_field("%*cTarget codec buffer level: %u (0x%02x)", ++ BASE_INDENT + 2, ' ', level, level); ++ ++ l2cap_frame_get_le16(frame, &level); ++ print_field("%*cInitial codec buffer level: %u (0x%02x)", ++ BASE_INDENT + 2, ' ', level, level); ++ ++ l2cap_frame_get_u8(frame, &cap); ++ print_field("%*cSRA max rate: %g (0x%02x)", ++ BASE_INDENT + 2, ' ', cap / 10000.0, cap); ++ ++ l2cap_frame_get_u8(frame, &cap); ++ print_field("%*cSRA averaging time: %us (0x%02x)", ++ BASE_INDENT + 2, ' ', cap, cap); ++ ++ l2cap_frame_get_le16(frame, &level); ++ print_field("%*cGood working codec buffer level: %u (0x%02x)", ++ BASE_INDENT + 2, ' ', level, level); ++ } ++ ++ return true; ++} ++ ++static bool codec_vendor_aptx_hd_cfg(uint8_t losc, struct l2cap_frame *frame) ++{ ++ uint8_t cap = 0; ++ ++ if (losc != 5) ++ 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); ++ ++ /* reserved */ ++ l2cap_frame_get_u8(frame, &cap); ++ l2cap_frame_get_u8(frame, &cap); ++ l2cap_frame_get_u8(frame, &cap); ++ l2cap_frame_get_u8(frame, &cap); ++ ++ return true; ++} ++ + static bool codec_vendor_cfg(uint8_t losc, struct l2cap_frame *frame) + { + uint32_t vendor_id = 0; + uint16_t codec_id = 0; ++ size_t i; + + if (losc < 6) + return false; +@@ -590,10 +861,11 @@ static bool codec_vendor_cfg(uint8_t losc, struct l2cap_frame *frame) + 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) +- return codec_vendor_aptx_cfg(losc, frame); +- else if (vendor_id == LDAC_VENDOR_ID && codec_id == LDAC_CODEC_ID) +- return codec_vendor_ldac(losc, frame); ++ for (i = 0; i < sizeof(vndcodecs)/sizeof(*vndcodecs); i++) { ++ if (vndcodecs[i].vendor_id == vendor_id && ++ vndcodecs[i].codec_id == codec_id) ++ return vndcodecs[i].codec_vendor_cfg(losc, frame); ++ } + + packet_hexdump(frame->data, losc); + l2cap_frame_pull(frame, frame, losc); +-- +2.21.0 + + +From fcad924159d772586fe9e402985fee64ae29b70a Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Thu, 3 Jan 2019 13:45:43 -0300 +Subject: [PATCH 12/31] a2dp: Expose remote SEP + +This implements MediaEndpoint for remote SEP which can be used by +clients to switch configuration on demand. +--- + profiles/audio/a2dp.c | 351 ++++++++++++++++++++++++++++++++++++++++- + profiles/audio/a2dp.h | 1 + + profiles/audio/avdtp.c | 10 ++ + profiles/audio/avdtp.h | 4 + + profiles/audio/media.c | 8 + + 5 files changed, 367 insertions(+), 7 deletions(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 344459332..4025776aa 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -27,7 +27,10 @@ + #include + #endif + ++#define _GNU_SOURCE ++ + #include ++#include + #include + + #include +@@ -38,14 +41,19 @@ + #include "lib/sdp_lib.h" + #include "lib/uuid.h" + ++#include "gdbus/gdbus.h" ++ + #include "src/plugin.h" + #include "src/adapter.h" + #include "src/device.h" ++#include "src/dbus-common.h" ++#include "src/error.h" + #include "src/profile.h" + #include "src/service.h" + #include "src/log.h" + #include "src/sdpd.h" + #include "src/shared/queue.h" ++#include "src/shared/util.h" + + #include "btio/btio.h" + +@@ -63,6 +71,8 @@ + + #define AVDTP_PSM 25 + ++#define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" ++ + struct a2dp_sep { + struct a2dp_server *server; + struct a2dp_endpoint *endpoint; +@@ -93,6 +103,7 @@ struct a2dp_setup_cb { + }; + + struct a2dp_setup { ++ struct a2dp_channel *chan; + struct avdtp *session; + struct a2dp_sep *sep; + struct avdtp_remote_sep *rsep; +@@ -121,6 +132,12 @@ struct a2dp_server { + struct queue *channels; + }; + ++struct a2dp_remote_sep { ++ struct a2dp_channel *chan; ++ char *path; ++ struct avdtp_remote_sep *sep; ++}; ++ + struct a2dp_channel { + struct a2dp_server *server; + struct btd_device *device; +@@ -129,6 +146,7 @@ struct a2dp_channel { + unsigned int state_id; + unsigned int auth_id; + struct avdtp *session; ++ struct queue *seps; + }; + + static GSList *servers = NULL; +@@ -144,12 +162,42 @@ static struct a2dp_setup *setup_ref(struct a2dp_setup *setup) + return setup; + } + ++static bool match_by_session(const void *data, const void *user_data) ++{ ++ const struct a2dp_channel *chan = data; ++ const struct avdtp *session = user_data; ++ ++ return chan->session == session; ++} ++ ++static struct a2dp_channel *find_channel(struct avdtp *session) ++{ ++ GSList *l; ++ ++ for (l = servers; l; l = g_slist_next(l)) { ++ struct a2dp_server *server = l->data; ++ struct a2dp_channel *chan; ++ ++ chan = queue_find(server->channels, match_by_session, session); ++ if (chan) ++ return chan; ++ } ++ ++ return NULL; ++} ++ + static struct a2dp_setup *setup_new(struct avdtp *session) + { + struct a2dp_setup *setup; ++ struct a2dp_channel *chan; ++ ++ chan = find_channel(session); ++ if (!chan) ++ return NULL; + + setup = g_new0(struct a2dp_setup, 1); + setup->session = avdtp_ref(session); ++ setup->chan = find_channel(session); + setups = g_slist_append(setups, setup); + + return setup; +@@ -1299,6 +1347,14 @@ static struct a2dp_server *find_server(GSList *list, struct btd_adapter *a) + return NULL; + } + ++static void remove_remote_sep(void *data) ++{ ++ struct a2dp_remote_sep *sep = data; ++ ++ g_dbus_unregister_interface(btd_get_dbus_connection(), sep->path, ++ MEDIA_ENDPOINT_INTERFACE); ++} ++ + static void channel_free(void *data) + { + struct a2dp_channel *chan = data; +@@ -1316,6 +1372,7 @@ static void channel_free(void *data) + + avdtp_remove_state_cb(chan->state_id); + ++ queue_destroy(chan->seps, remove_remote_sep); + g_free(chan); + } + +@@ -1371,6 +1428,7 @@ static struct a2dp_channel *channel_new(struct a2dp_server *server, + chan = g_new0(struct a2dp_channel, 1); + chan->server = server; + chan->device = device; ++ chan->seps = queue_new(); + chan->state_id = avdtp_add_state_cb(device, avdtp_state_cb, chan); + + if (!queue_push_tail(server->channels, chan)) { +@@ -1805,16 +1863,11 @@ void a2dp_remove_sep(struct a2dp_sep *sep) + a2dp_unregister_sep(sep); + } + +-static void select_cb(struct a2dp_setup *setup, void *ret, int size) ++static void setup_add_caps(struct a2dp_setup *setup, uint8_t *caps, size_t size) + { + struct avdtp_service_capability *media_transport, *media_codec; + struct avdtp_media_codec_capability *cap; + +- if (size < 0) { +- DBG("Endpoint replied an invalid configuration"); +- goto done; +- } +- + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + +@@ -1823,13 +1876,23 @@ static void select_cb(struct a2dp_setup *setup, void *ret, int size) + cap = g_malloc0(sizeof(*cap) + size); + cap->media_type = AVDTP_MEDIA_TYPE_AUDIO; + cap->media_codec_type = setup->sep->codec; +- memcpy(cap->data, ret, size); ++ memcpy(cap->data, caps, size); + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap, + sizeof(*cap) + size); + + setup->caps = g_slist_append(setup->caps, media_codec); + g_free(cap); ++} ++ ++static void select_cb(struct a2dp_setup *setup, void *ret, int size) ++{ ++ if (size < 0) { ++ DBG("Endpoint replied an invalid configuration"); ++ goto done; ++ } ++ ++ setup_add_caps(setup, ret, size); + + done: + finalize_select(setup); +@@ -1885,6 +1948,277 @@ static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type, + return a2dp_find_sep(session, l, NULL); + } + ++struct client { ++ const char *sender; ++ const char *path; ++}; ++ ++static int match_client(const void *data, const void *user_data) ++{ ++ struct a2dp_sep *sep = (void *) data; ++ const struct a2dp_endpoint *endpoint = sep->endpoint; ++ const struct client *client = user_data; ++ ++ if (strcmp(client->sender, endpoint->get_name(sep, sep->user_data))) ++ return -1; ++ ++ return strcmp(client->path, endpoint->get_path(sep, sep->user_data)); ++} ++ ++static struct a2dp_sep *find_sep(struct a2dp_server *server, const char *sender, ++ const char *path) ++{ ++ GSList *l; ++ struct client client = { sender, path }; ++ ++ l = g_slist_find_custom(server->sources, &client, match_client); ++ if (l) ++ return l->data; ++ ++ l = g_slist_find_custom(server->sinks, &client, match_client); ++ if (l) ++ return l->data; ++ ++ return NULL; ++} ++ ++static int parse_properties(DBusMessageIter *props, uint8_t **caps, int *size) ++{ ++ while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { ++ const char *key; ++ DBusMessageIter value, entry; ++ int var; ++ ++ dbus_message_iter_recurse(props, &entry); ++ dbus_message_iter_get_basic(&entry, &key); ++ ++ dbus_message_iter_next(&entry); ++ dbus_message_iter_recurse(&entry, &value); ++ ++ var = dbus_message_iter_get_arg_type(&value); ++ if (strcasecmp(key, "Capabilities") == 0) { ++ DBusMessageIter array; ++ ++ if (var != DBUS_TYPE_ARRAY) ++ return -EINVAL; ++ ++ dbus_message_iter_recurse(&value, &array); ++ dbus_message_iter_get_fixed_array(&array, caps, size); ++ return 0; ++ } ++ ++ dbus_message_iter_next(props); ++ } ++ ++ return -EINVAL; ++} ++ ++static int a2dp_reconfig(struct a2dp_channel *chan, const char *sender, ++ struct a2dp_sep *lsep, struct a2dp_remote_sep *rsep, ++ uint8_t *caps, int size) ++{ ++ struct a2dp_setup *setup; ++ const struct queue_entry *entry; ++ int err; ++ ++ setup = a2dp_setup_get(chan->session); ++ if (!setup) ++ return -ENOMEM; ++ ++ setup->sep = lsep; ++ setup->rsep = rsep->sep; ++ ++ setup_add_caps(setup, caps, size); ++ ++ /* Check for existing stream and close it */ ++ for (entry = queue_get_entries(chan->server->seps); entry; ++ entry = entry->next) { ++ struct a2dp_sep *tmp = entry->data; ++ ++ /* Attempt to reconfigure if a stream already exists */ ++ if (tmp->stream) { ++ /* Only allow switching sep from the same sender */ ++ if (strcmp(sender, tmp->endpoint->get_name(tmp, ++ tmp->user_data))) ++ return -EPERM; ++ ++ err = avdtp_close(chan->session, tmp->stream, FALSE); ++ if (err < 0) { ++ error("avdtp_close: %s", strerror(-err)); ++ return err; ++ } ++ ++ setup->reconfigure = TRUE; ++ ++ return 0; ++ } ++ } ++ ++ err = avdtp_set_configuration(setup->session, setup->rsep, ++ lsep->lsep, ++ setup->caps, ++ &setup->stream); ++ if (err < 0) { ++ error("avdtp_set_configuration: %s", strerror(-err)); ++ return err; ++ } ++ ++ return 0; ++} ++ ++static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, ++ void *data) ++{ ++ struct a2dp_remote_sep *rsep = data; ++ struct a2dp_channel *chan = rsep->chan; ++ struct a2dp_sep *lsep; ++ struct avdtp_service_capability *service; ++ struct avdtp_media_codec_capability *codec; ++ DBusMessageIter args, props; ++ const char *sender, *path; ++ uint8_t *caps; ++ int err, size = 0; ++ ++ sender = dbus_message_get_sender(msg); ++ ++ dbus_message_iter_init(msg, &args); ++ ++ dbus_message_iter_get_basic(&args, &path); ++ dbus_message_iter_next(&args); ++ ++ lsep = find_sep(chan->server, sender, path); ++ if (!lsep) ++ return btd_error_invalid_args(msg); ++ ++ /* Check if SEPs are no the same role */ ++ if (avdtp_get_type(rsep->sep) == lsep->type) ++ return btd_error_invalid_args(msg); ++ ++ service = avdtp_get_codec(rsep->sep); ++ codec = (struct avdtp_media_codec_capability *) service->data; ++ ++ /* Check if codec match */ ++ if (!endpoint_match_codec_ind(chan->session, codec, lsep)) ++ return btd_error_invalid_args(msg); ++ ++ dbus_message_iter_recurse(&args, &props); ++ if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) ++ return btd_error_invalid_args(msg); ++ ++ if (parse_properties(&props, &caps, &size) < 0) ++ return btd_error_invalid_args(msg); ++ ++ err = a2dp_reconfig(chan, sender, lsep, rsep, caps, size); ++ if (err < 0) ++ return btd_error_failed(msg, strerror(-err)); ++ ++ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); ++} ++ ++static const GDBusMethodTable sep_methods[] = { ++ { GDBUS_EXPERIMENTAL_ASYNC_METHOD("SetConfiguration", ++ GDBUS_ARGS({ "endpoint", "o" }, ++ { "properties", "a{sv}" } ), ++ NULL, set_configuration) }, ++ { }, ++}; ++ ++static gboolean get_uuid(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct a2dp_remote_sep *sep = data; ++ const char *uuid; ++ ++ switch (avdtp_get_type(sep->sep)) { ++ case AVDTP_SEP_TYPE_SOURCE: ++ uuid = A2DP_SOURCE_UUID; ++ break; ++ case AVDTP_SEP_TYPE_SINK: ++ uuid = A2DP_SOURCE_UUID; ++ break; ++ default: ++ uuid = ""; ++ } ++ ++ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); ++ ++ return TRUE; ++} ++ ++static gboolean get_codec(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct a2dp_remote_sep *sep = data; ++ struct avdtp_service_capability *cap = avdtp_get_codec(sep->sep); ++ struct avdtp_media_codec_capability *codec = (void *) cap->data; ++ ++ dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, ++ &codec->media_codec_type); ++ ++ return TRUE; ++} ++ ++static gboolean get_capabilities(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct a2dp_remote_sep *sep = data; ++ struct avdtp_service_capability *service = avdtp_get_codec(sep->sep); ++ struct avdtp_media_codec_capability *codec = (void *) service->data; ++ uint8_t *caps = codec->data; ++ DBusMessageIter array; ++ ++ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, ++ DBUS_TYPE_BYTE_AS_STRING, &array); ++ ++ dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &caps, ++ service->length - sizeof(*codec)); ++ ++ dbus_message_iter_close_container(iter, &array); ++ ++ return TRUE; ++} ++ ++static const GDBusPropertyTable sep_properties[] = { ++ { "UUID", "s", get_uuid, NULL, NULL, ++ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, ++ { "Codec", "y", get_codec, NULL, NULL, ++ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, ++ { "Capabilities", "ay", get_capabilities, NULL, NULL, ++ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, ++ { } ++}; ++ ++static void remote_sep_free(void *data) ++{ ++ struct a2dp_remote_sep *sep = data; ++ ++ free(sep->path); ++ free(sep); ++} ++ ++static void register_remote_sep(void *data, void *user_data) ++{ ++ struct avdtp_remote_sep *rsep = data; ++ struct a2dp_setup *setup = user_data; ++ struct a2dp_remote_sep *sep; ++ ++ sep = new0(struct a2dp_remote_sep, 1); ++ sep->chan = setup->chan; ++ sep->sep = rsep; ++ asprintf(&sep->path, "%s/sep%d", device_get_path(setup->chan->device), ++ avdtp_get_seid(rsep)); ++ ++ if (g_dbus_register_interface(btd_get_dbus_connection(), ++ sep->path, MEDIA_ENDPOINT_INTERFACE, ++ sep_methods, NULL, sep_properties, ++ sep, remote_sep_free) == FALSE) { ++ error("Could not register remote sep %s", sep->path); ++ remote_sep_free(sep); ++ } ++ ++ queue_push_tail(setup->chan->seps, sep); ++} ++ + static void discover_cb(struct avdtp *session, GSList *seps, + struct avdtp_error *err, void *user_data) + { +@@ -1895,6 +2229,9 @@ static void discover_cb(struct avdtp *session, GSList *seps, + setup->seps = seps; + setup->err = err; + ++ if (!err && queue_isempty(setup->chan->seps)) ++ g_slist_foreach(seps, register_remote_sep, setup); ++ + finalize_discover(setup); + } + +diff --git a/profiles/audio/a2dp.h b/profiles/audio/a2dp.h +index 2c388bb68..7f38c75f3 100644 +--- a/profiles/audio/a2dp.h ++++ b/profiles/audio/a2dp.h +@@ -32,6 +32,7 @@ typedef void (*a2dp_endpoint_config_t) (struct a2dp_setup *setup, gboolean ret); + + struct a2dp_endpoint { + const char *(*get_name) (struct a2dp_sep *sep, void *user_data); ++ const char *(*get_path) (struct a2dp_sep *sep, void *user_data); + size_t (*get_capabilities) (struct a2dp_sep *sep, + uint8_t **capabilities, + void *user_data); +diff --git a/profiles/audio/avdtp.c b/profiles/audio/avdtp.c +index 2cb3c8a00..cc4322d10 100644 +--- a/profiles/audio/avdtp.c ++++ b/profiles/audio/avdtp.c +@@ -3161,6 +3161,16 @@ static int process_queue(struct avdtp *session) + return send_req(session, FALSE, req); + } + ++uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep) ++{ ++ return sep->seid; ++} ++ ++uint8_t avdtp_get_type(struct avdtp_remote_sep *sep) ++{ ++ return sep->type; ++} ++ + struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep) + { + return sep->codec; +diff --git a/profiles/audio/avdtp.h b/profiles/audio/avdtp.h +index 621a6e3cf..e5fc40c89 100644 +--- a/profiles/audio/avdtp.h ++++ b/profiles/audio/avdtp.h +@@ -223,6 +223,10 @@ struct avdtp *avdtp_ref(struct avdtp *session); + struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + void *data, int size); + ++uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep); ++ ++uint8_t avdtp_get_type(struct avdtp_remote_sep *sep); ++ + struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep); + + int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, +diff --git a/profiles/audio/media.c b/profiles/audio/media.c +index 23d15611b..9b833b6aa 100644 +--- a/profiles/audio/media.c ++++ b/profiles/audio/media.c +@@ -488,6 +488,13 @@ static const char *get_name(struct a2dp_sep *sep, void *user_data) + return endpoint->sender; + } + ++static const char *get_path(struct a2dp_sep *sep, void *user_data) ++{ ++ struct media_endpoint *endpoint = user_data; ++ ++ return endpoint->path; ++} ++ + static size_t get_capabilities(struct a2dp_sep *sep, uint8_t **capabilities, + void *user_data) + { +@@ -578,6 +585,7 @@ static void set_delay(struct a2dp_sep *sep, uint16_t delay, void *user_data) + + static struct a2dp_endpoint a2dp_endpoint = { + .get_name = get_name, ++ .get_path = get_path, + .get_capabilities = get_capabilities, + .select_configuration = select_config, + .set_configuration = set_config, +-- +2.21.0 + + +From dc9b175cc89621a2abbae5b661bfc61c68191abe Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 4 Jan 2019 16:22:05 -0300 +Subject: [PATCH 13/31] doc/media-api: Add Endpoint property to MediaTransport + +Adds endpoint object to MediaTransport so application can resolve which +MediaEndpoint is in use. +--- + doc/media-api.txt | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/doc/media-api.txt b/doc/media-api.txt +index b5ad2db12..93c3490b6 100644 +--- a/doc/media-api.txt ++++ b/doc/media-api.txt +@@ -604,3 +604,8 @@ Properties object Device [readonly] + acquired by the sender. + + Possible Values: 0-127 ++ ++ object Endpoint [readonly, optional, experimental] ++ ++ Endpoint object which the transport is associated ++ with. +-- +2.21.0 + + +From e7795ccbc1810a7a398fb059638f289644447ab1 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 4 Jan 2019 16:24:18 -0300 +Subject: [PATCH 14/31] a2dp: Implement MediaTransport.Endpoint + +This implements MediaTransport.Endpoint property which exposes what +endpoint is being used by the transport. +--- + profiles/audio/a2dp.c | 91 +++++++++++++++++++++++++++++--------- + profiles/audio/a2dp.h | 1 + + profiles/audio/media.c | 5 ++- + profiles/audio/transport.c | 28 +++++++++++- + profiles/audio/transport.h | 1 + + 5 files changed, 102 insertions(+), 24 deletions(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 4025776aa..4fa01894a 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -106,7 +106,7 @@ struct a2dp_setup { + struct a2dp_channel *chan; + struct avdtp *session; + struct a2dp_sep *sep; +- struct avdtp_remote_sep *rsep; ++ struct a2dp_remote_sep *rsep; + struct avdtp_stream *stream; + struct avdtp_error *err; + avdtp_set_configuration_cb setconf_cb; +@@ -1065,6 +1065,24 @@ static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep, + return TRUE; + } + ++static bool match_remote_sep(const void *data, const void *user_data) ++{ ++ const struct a2dp_remote_sep *sep = data; ++ const struct avdtp_remote_sep *rsep = user_data; ++ ++ return sep->sep == rsep; ++} ++ ++static struct a2dp_remote_sep *find_remote_sep(struct a2dp_channel *chan, ++ struct a2dp_sep *sep) ++{ ++ struct avdtp_remote_sep *rsep; ++ ++ rsep = avdtp_find_remote_sep(chan->session, sep->lsep); ++ ++ return queue_find(chan->seps, match_remote_sep, rsep); ++} ++ + static gboolean a2dp_reconfigure(gpointer data) + { + struct a2dp_setup *setup = data; +@@ -1074,14 +1092,14 @@ static gboolean a2dp_reconfigure(gpointer data) + struct avdtp_service_capability *cap; + + if (setup->rsep) { +- cap = avdtp_get_codec(setup->rsep); ++ cap = avdtp_get_codec(setup->rsep->sep); + rsep_codec = (struct avdtp_media_codec_capability *) cap->data; + } + + if (!setup->rsep || sep->codec != rsep_codec->media_codec_type) +- setup->rsep = avdtp_find_remote_sep(setup->session, sep->lsep); ++ setup->rsep = find_remote_sep(setup->chan, sep); + +- posix_err = avdtp_set_configuration(setup->session, setup->rsep, ++ posix_err = avdtp_set_configuration(setup->session, setup->rsep->sep, + sep->lsep, + setup->caps, + &setup->stream); +@@ -1097,6 +1115,16 @@ failed: + return FALSE; + } + ++static struct a2dp_remote_sep *get_remote_sep(struct a2dp_channel *chan, ++ struct avdtp_stream *stream) ++{ ++ struct avdtp_remote_sep *rsep; ++ ++ rsep = avdtp_stream_get_remote_sep(stream); ++ ++ return queue_find(chan->seps, match_remote_sep, rsep); ++} ++ + static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +@@ -1121,7 +1149,7 @@ static void close_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + } + + if (!setup->rsep) +- setup->rsep = avdtp_stream_get_remote_sep(stream); ++ setup->rsep = get_remote_sep(setup->chan, stream); + + if (setup->reconfigure) + g_timeout_add(RECONFIGURE_TIMEOUT, a2dp_reconfigure, setup); +@@ -1347,10 +1375,23 @@ static struct a2dp_server *find_server(GSList *list, struct btd_adapter *a) + return NULL; + } + ++static void remote_sep_free(void *data) ++{ ++ struct a2dp_remote_sep *sep = data; ++ ++ free(sep->path); ++ free(sep); ++} ++ + static void remove_remote_sep(void *data) + { + struct a2dp_remote_sep *sep = data; + ++ if (!sep->path) { ++ remote_sep_free(sep); ++ return; ++ } ++ + g_dbus_unregister_interface(btd_get_dbus_connection(), sep->path, + MEDIA_ENDPOINT_INTERFACE); + } +@@ -2026,7 +2067,7 @@ static int a2dp_reconfig(struct a2dp_channel *chan, const char *sender, + return -ENOMEM; + + setup->sep = lsep; +- setup->rsep = rsep->sep; ++ setup->rsep = rsep; + + setup_add_caps(setup, caps, size); + +@@ -2054,7 +2095,7 @@ static int a2dp_reconfig(struct a2dp_channel *chan, const char *sender, + } + } + +- err = avdtp_set_configuration(setup->session, setup->rsep, ++ err = avdtp_set_configuration(setup->session, setup->rsep->sep, + lsep->lsep, + setup->caps, + &setup->stream); +@@ -2188,14 +2229,6 @@ static const GDBusPropertyTable sep_properties[] = { + { } + }; + +-static void remote_sep_free(void *data) +-{ +- struct a2dp_remote_sep *sep = data; +- +- free(sep->path); +- free(sep); +-} +- + static void register_remote_sep(void *data, void *user_data) + { + struct avdtp_remote_sep *rsep = data; +@@ -2205,6 +2238,10 @@ static void register_remote_sep(void *data, void *user_data) + sep = new0(struct a2dp_remote_sep, 1); + sep->chan = setup->chan; + sep->sep = rsep; ++ ++ if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) ++ goto done; ++ + asprintf(&sep->path, "%s/sep%d", device_get_path(setup->chan->device), + avdtp_get_seid(rsep)); + +@@ -2213,9 +2250,13 @@ static void register_remote_sep(void *data, void *user_data) + sep_methods, NULL, sep_properties, + sep, remote_sep_free) == FALSE) { + error("Could not register remote sep %s", sep->path); +- remote_sep_free(sep); ++ free(sep->path); ++ sep->path = NULL; + } + ++ DBG("Found remote SEP: %s", sep->path); ++ ++done: + queue_push_tail(setup->chan->seps, sep); + } + +@@ -2283,14 +2324,14 @@ unsigned int a2dp_select_capabilities(struct avdtp *session, + cb_data->user_data = user_data; + + setup->sep = sep; +- setup->rsep = avdtp_find_remote_sep(session, sep->lsep); ++ setup->rsep = find_remote_sep(setup->chan, sep); + + if (setup->rsep == NULL) { + error("Could not find remote sep"); + goto fail; + } + +- service = avdtp_get_codec(setup->rsep); ++ service = avdtp_get_codec(setup->rsep->sep); + codec = (struct avdtp_media_codec_capability *) service->data; + + err = sep->endpoint->select_configuration(sep, codec->data, +@@ -2384,13 +2425,13 @@ unsigned int a2dp_config(struct avdtp *session, struct a2dp_sep *sep, + break; + } + +- setup->rsep = avdtp_find_remote_sep(session, sep->lsep); ++ setup->rsep = find_remote_sep(setup->chan, sep); + if (setup->rsep == NULL) { + error("No matching ACP and INT SEPs found"); + goto failed; + } + +- posix_err = avdtp_set_configuration(session, setup->rsep, ++ posix_err = avdtp_set_configuration(session, setup->rsep->sep, + sep->lsep, caps, + &setup->stream); + if (posix_err < 0) { +@@ -2632,6 +2673,16 @@ struct btd_device *a2dp_setup_get_device(struct a2dp_setup *setup) + return avdtp_get_device(setup->session); + } + ++const char *a2dp_setup_remote_path(struct a2dp_setup *setup) ++{ ++ if (setup->rsep) { ++ if (setup->rsep->path) ++ return setup->rsep->path; ++ } ++ ++ return NULL; ++} ++ + static int a2dp_source_probe(struct btd_service *service) + { + struct btd_device *dev = btd_service_get_device(service); +diff --git a/profiles/audio/a2dp.h b/profiles/audio/a2dp.h +index 7f38c75f3..19466a428 100644 +--- a/profiles/audio/a2dp.h ++++ b/profiles/audio/a2dp.h +@@ -91,4 +91,5 @@ gboolean a2dp_sep_lock(struct a2dp_sep *sep, struct avdtp *session); + gboolean a2dp_sep_unlock(struct a2dp_sep *sep, struct avdtp *session); + struct avdtp_stream *a2dp_sep_get_stream(struct a2dp_sep *sep); + struct btd_device *a2dp_setup_get_device(struct a2dp_setup *setup); ++const char *a2dp_setup_remote_path(struct a2dp_setup *setup); + struct avdtp *a2dp_avdtp_get(struct btd_device *device); +diff --git a/profiles/audio/media.c b/profiles/audio/media.c +index 9b833b6aa..e651275b4 100644 +--- a/profiles/audio/media.c ++++ b/profiles/audio/media.c +@@ -429,8 +429,9 @@ static gboolean set_configuration(struct media_endpoint *endpoint, + if (transport != NULL) + return FALSE; + +- transport = media_transport_create(device, configuration, size, +- endpoint); ++ transport = media_transport_create(device, ++ a2dp_setup_remote_path(data->setup), ++ configuration, size, endpoint); + if (transport == NULL) + return FALSE; + +diff --git a/profiles/audio/transport.c b/profiles/audio/transport.c +index b9d357ec2..7eb115fc6 100644 +--- a/profiles/audio/transport.c ++++ b/profiles/audio/transport.c +@@ -91,6 +91,7 @@ struct a2dp_transport { + struct media_transport { + char *path; /* Transport object path */ + struct btd_device *device; /* Transport device */ ++ const char *remote_endpoint; /* Transport remote SEP */ + struct media_endpoint *endpoint; /* Transport endpoint */ + struct media_owner *owner; /* Transport owner */ + uint8_t *configuration; /* Transport configuration */ +@@ -688,6 +689,24 @@ static void set_volume(const GDBusPropertyTable *property, + avrcp_set_volume(transport->device, volume, notify); + } + ++static gboolean endpoint_exists(const GDBusPropertyTable *property, void *data) ++{ ++ struct media_transport *transport = data; ++ ++ return transport->remote_endpoint != NULL; ++} ++ ++static gboolean get_endpoint(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct media_transport *transport = data; ++ ++ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, ++ &transport->remote_endpoint); ++ ++ return TRUE; ++} ++ + static const GDBusMethodTable transport_methods[] = { + { GDBUS_ASYNC_METHOD("Acquire", + NULL, +@@ -711,6 +730,8 @@ static const GDBusPropertyTable transport_properties[] = { + { "State", "s", get_state }, + { "Delay", "q", get_delay, NULL, delay_exists }, + { "Volume", "q", get_volume, set_volume, volume_exists }, ++ { "Endpoint", "o", get_endpoint, NULL, endpoint_exists, ++ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { } + }; + +@@ -835,6 +856,7 @@ static int media_transport_init_sink(struct media_transport *transport) + } + + struct media_transport *media_transport_create(struct btd_device *device, ++ const char *remote_endpoint, + uint8_t *configuration, + size_t size, void *data) + { +@@ -849,8 +871,10 @@ struct media_transport *media_transport_create(struct btd_device *device, + transport->configuration = g_new(uint8_t, size); + memcpy(transport->configuration, configuration, size); + transport->size = size; +- transport->path = g_strdup_printf("%s/fd%d", device_get_path(device), +- fd++); ++ transport->remote_endpoint = remote_endpoint; ++ transport->path = g_strdup_printf("%s/fd%d", ++ remote_endpoint ? remote_endpoint : ++ device_get_path(device), fd++); + transport->fd = -1; + + uuid = media_endpoint_get_uuid(endpoint); +diff --git a/profiles/audio/transport.h b/profiles/audio/transport.h +index 505ad5c54..ac542bf6c 100644 +--- a/profiles/audio/transport.h ++++ b/profiles/audio/transport.h +@@ -25,6 +25,7 @@ + struct media_transport; + + struct media_transport *media_transport_create(struct btd_device *device, ++ const char *remote_endpoint, + uint8_t *configuration, + size_t size, void *data); + +-- +2.21.0 + + +From 7ba5782208069a27ed8207b6b43e5ba1b4a5c9c6 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Tue, 8 Jan 2019 10:34:42 -0300 +Subject: [PATCH 15/31] a2dp: Cache remote endpoints + +In order to always have the Endpoint interface available the remote +endpoints needs to be cached since the remote stack may config a stream +on its own there may not be a chance to discover the endpoits available +which would make it impossible to switch endpoints. +--- + profiles/audio/a2dp.c | 724 +++++++++++++++++++++++++---------------- + profiles/audio/avdtp.c | 46 ++- + profiles/audio/avdtp.h | 5 + + 3 files changed, 492 insertions(+), 283 deletions(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 4fa01894a..6975682c9 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -1441,6 +1441,410 @@ static gboolean disconnect_cb(GIOChannel *io, GIOCondition cond, gpointer data) + return FALSE; + } + ++static void caps_add_codec(GSList **l, uint8_t codec, uint8_t *caps, ++ size_t size) ++{ ++ struct avdtp_service_capability *media_transport, *media_codec; ++ struct avdtp_media_codec_capability *cap; ++ ++ media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, ++ NULL, 0); ++ ++ *l = g_slist_append(*l, media_transport); ++ ++ cap = g_malloc0(sizeof(*cap) + size); ++ cap->media_type = AVDTP_MEDIA_TYPE_AUDIO; ++ cap->media_codec_type = codec; ++ memcpy(cap->data, caps, size); ++ ++ media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap, ++ sizeof(*cap) + size); ++ ++ *l = g_slist_append(*l, media_codec); ++ g_free(cap); ++} ++ ++struct client { ++ const char *sender; ++ const char *path; ++}; ++ ++static int match_client(const void *data, const void *user_data) ++{ ++ struct a2dp_sep *sep = (void *) data; ++ const struct a2dp_endpoint *endpoint = sep->endpoint; ++ const struct client *client = user_data; ++ ++ if (strcmp(client->sender, endpoint->get_name(sep, sep->user_data))) ++ return -1; ++ ++ return strcmp(client->path, endpoint->get_path(sep, sep->user_data)); ++} ++ ++static struct a2dp_sep *find_sep(struct a2dp_server *server, uint8_t type, ++ const char *sender, const char *path) ++{ ++ GSList *l; ++ struct client client = { sender, path }; ++ ++ l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks; ++ ++ l = g_slist_find_custom(l, &client, match_client); ++ if (l) ++ return l->data; ++ ++ return NULL; ++} ++ ++static int parse_properties(DBusMessageIter *props, uint8_t **caps, int *size) ++{ ++ while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { ++ const char *key; ++ DBusMessageIter value, entry; ++ int var; ++ ++ dbus_message_iter_recurse(props, &entry); ++ dbus_message_iter_get_basic(&entry, &key); ++ ++ dbus_message_iter_next(&entry); ++ dbus_message_iter_recurse(&entry, &value); ++ ++ var = dbus_message_iter_get_arg_type(&value); ++ if (strcasecmp(key, "Capabilities") == 0) { ++ DBusMessageIter array; ++ ++ if (var != DBUS_TYPE_ARRAY) ++ return -EINVAL; ++ ++ dbus_message_iter_recurse(&value, &array); ++ dbus_message_iter_get_fixed_array(&array, caps, size); ++ return 0; ++ } ++ ++ dbus_message_iter_next(props); ++ } ++ ++ return -EINVAL; ++} ++ ++static void reconfig_cb(struct avdtp *session, struct a2dp_sep *sep, ++ struct avdtp_stream *stream, int err, void *user_data) ++{ ++ DBusMessage *msg = user_data; ++ ++ if (err) ++ g_dbus_send_message(btd_get_dbus_connection(), ++ btd_error_failed(msg, strerror(-err))); ++ else ++ g_dbus_send_reply(btd_get_dbus_connection(), msg, ++ DBUS_TYPE_INVALID); ++ ++ dbus_message_unref(msg); ++} ++ ++static int a2dp_reconfig(struct a2dp_channel *chan, const char *sender, ++ struct a2dp_sep *lsep, struct a2dp_remote_sep *rsep, ++ uint8_t *caps, int size, void *user_data) ++{ ++ struct a2dp_setup *setup; ++ struct a2dp_setup_cb *cb_data; ++ GSList *l; ++ int err; ++ ++ setup = a2dp_setup_get(chan->session); ++ if (!setup) ++ return -ENOMEM; ++ ++ cb_data = setup_cb_new(setup); ++ cb_data->config_cb = reconfig_cb; ++ cb_data->user_data = user_data; ++ ++ setup->sep = lsep; ++ setup->rsep = rsep; ++ ++ caps_add_codec(&setup->caps, setup->sep->codec, caps, size); ++ ++ l = avdtp_get_type(rsep->sep) == AVDTP_SEP_TYPE_SINK ? ++ chan->server->sources : ++ chan->server->sinks; ++ ++ /* Check for existing stream and close it */ ++ for (; l; l = g_slist_next(l)) { ++ struct a2dp_sep *tmp = l->data; ++ ++ /* Attempt to reconfigure if a stream already exists */ ++ if (tmp->stream) { ++ /* Only allow switching sep from the same sender */ ++ if (strcmp(sender, tmp->endpoint->get_name(tmp, ++ tmp->user_data))) ++ return -EPERM; ++ ++ err = avdtp_close(chan->session, tmp->stream, FALSE); ++ if (err < 0) { ++ error("avdtp_close: %s", strerror(-err)); ++ goto fail; ++ } ++ ++ setup->reconfigure = TRUE; ++ ++ return 0; ++ } ++ } ++ ++ err = avdtp_set_configuration(setup->session, setup->rsep->sep, ++ lsep->lsep, ++ setup->caps, ++ &setup->stream); ++ if (err < 0) { ++ error("avdtp_set_configuration: %s", strerror(-err)); ++ goto fail; ++ } ++ ++ return 0; ++ ++fail: ++ setup_unref(setup); ++ return err; ++} ++ ++static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, ++ void *data) ++{ ++ struct a2dp_remote_sep *rsep = data; ++ struct a2dp_channel *chan = rsep->chan; ++ struct a2dp_sep *lsep = NULL; ++ struct avdtp_service_capability *service; ++ struct avdtp_media_codec_capability *codec; ++ DBusMessageIter args, props; ++ const char *sender, *path; ++ uint8_t *caps; ++ int err, size = 0; ++ ++ sender = dbus_message_get_sender(msg); ++ ++ dbus_message_iter_init(msg, &args); ++ ++ dbus_message_iter_get_basic(&args, &path); ++ dbus_message_iter_next(&args); ++ ++ lsep = find_sep(chan->server, avdtp_get_type(rsep->sep), sender, path); ++ if (!lsep) ++ return btd_error_invalid_args(msg); ++ ++ service = avdtp_get_codec(rsep->sep); ++ codec = (struct avdtp_media_codec_capability *) service->data; ++ ++ /* Check if codec really matches */ ++ if (!endpoint_match_codec_ind(chan->session, codec, lsep)) ++ return btd_error_invalid_args(msg); ++ ++ dbus_message_iter_recurse(&args, &props); ++ if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) ++ return btd_error_invalid_args(msg); ++ ++ if (parse_properties(&props, &caps, &size) < 0) ++ return btd_error_invalid_args(msg); ++ ++ err = a2dp_reconfig(chan, sender, lsep, rsep, caps, size, ++ dbus_message_ref(msg)); ++ if (err < 0) { ++ dbus_message_unref(msg); ++ return btd_error_failed(msg, strerror(-err)); ++ } ++ ++ return NULL; ++} ++ ++static const GDBusMethodTable sep_methods[] = { ++ { GDBUS_EXPERIMENTAL_ASYNC_METHOD("SetConfiguration", ++ GDBUS_ARGS({ "endpoint", "o" }, ++ { "properties", "a{sv}" } ), ++ NULL, set_configuration) }, ++ { }, ++}; ++ ++static gboolean get_uuid(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct a2dp_remote_sep *sep = data; ++ const char *uuid; ++ ++ switch (avdtp_get_type(sep->sep)) { ++ case AVDTP_SEP_TYPE_SOURCE: ++ uuid = A2DP_SOURCE_UUID; ++ break; ++ case AVDTP_SEP_TYPE_SINK: ++ uuid = A2DP_SOURCE_UUID; ++ break; ++ default: ++ uuid = ""; ++ } ++ ++ dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); ++ ++ return TRUE; ++} ++ ++static gboolean get_codec(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct a2dp_remote_sep *sep = data; ++ struct avdtp_service_capability *cap = avdtp_get_codec(sep->sep); ++ struct avdtp_media_codec_capability *codec = (void *) cap->data; ++ ++ dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, ++ &codec->media_codec_type); ++ ++ return TRUE; ++} ++ ++static gboolean get_capabilities(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct a2dp_remote_sep *sep = data; ++ struct avdtp_service_capability *service = avdtp_get_codec(sep->sep); ++ struct avdtp_media_codec_capability *codec = (void *) service->data; ++ uint8_t *caps = codec->data; ++ DBusMessageIter array; ++ ++ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, ++ DBUS_TYPE_BYTE_AS_STRING, &array); ++ ++ dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &caps, ++ service->length - sizeof(*codec)); ++ ++ dbus_message_iter_close_container(iter, &array); ++ ++ return TRUE; ++} ++ ++static const GDBusPropertyTable sep_properties[] = { ++ { "UUID", "s", get_uuid, NULL, NULL, ++ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, ++ { "Codec", "y", get_codec, NULL, NULL, ++ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, ++ { "Capabilities", "ay", get_capabilities, NULL, NULL, ++ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, ++ { } ++}; ++ ++static void register_remote_sep(void *data, void *user_data) ++{ ++ struct avdtp_remote_sep *rsep = data; ++ struct a2dp_channel *chan = user_data; ++ struct a2dp_remote_sep *sep; ++ ++ sep = queue_find(chan->seps, match_remote_sep, rsep); ++ if (sep) ++ return; ++ ++ sep = new0(struct a2dp_remote_sep, 1); ++ sep->chan = chan; ++ sep->sep = rsep; ++ ++ if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) ++ goto done; ++ ++ asprintf(&sep->path, "%s/sep%d", device_get_path(chan->device), ++ avdtp_get_seid(rsep)); ++ ++ if (g_dbus_register_interface(btd_get_dbus_connection(), ++ sep->path, MEDIA_ENDPOINT_INTERFACE, ++ sep_methods, NULL, sep_properties, ++ sep, remote_sep_free) == FALSE) { ++ error("Could not register remote sep %s", sep->path); ++ free(sep->path); ++ sep->path = NULL; ++ goto done; ++ } ++ ++ DBG("Found remote SEP: %s", sep->path); ++ ++done: ++ queue_push_tail(chan->seps, sep); ++} ++ ++static void load_remote_sep(struct a2dp_channel *chan, GKeyFile *key_file, ++ char **seids) ++{ ++ struct avdtp_remote_sep *sep; ++ ++ if (!seids) ++ return; ++ ++ for (; *seids; seids++) { ++ uint8_t seid; ++ uint8_t type; ++ uint8_t codec; ++ char *value, caps[256]; ++ uint8_t data[128]; ++ int i, size; ++ GSList *l = NULL; ++ ++ if (sscanf(*seids, "%02hhx", &seid) != 1) ++ continue; ++ ++ value = g_key_file_get_string(key_file, "Endpoints", *seids, ++ NULL); ++ if (!value) ++ continue; ++ ++ if (sscanf(value, "%02hhx:%02hhx:%s", &type, &codec, ++ caps) != 3) { ++ warn("Unable to load Endpoint: seid %u", seid); ++ g_free(value); ++ continue; ++ } ++ ++ for (i = 0, size = strlen(caps); i < size; i += 2) { ++ uint8_t *tmp = data + i / 2; ++ ++ if (sscanf(caps + i, "%02hhx", tmp) != 1) { ++ warn("Unable to load Endpoint: seid %u", seid); ++ break; ++ } ++ } ++ ++ g_free(value); ++ ++ if (i != size) ++ continue; ++ ++ caps_add_codec(&l, codec, data, size / 2); ++ ++ sep = avdtp_register_remote_sep(chan->session, seid, type, l); ++ if (!sep) { ++ warn("Unable to register Endpoint: seid %u", seid); ++ continue; ++ } ++ ++ register_remote_sep(sep, chan); ++ } ++} ++ ++static void load_remote_seps(struct a2dp_channel *chan) ++{ ++ struct btd_device *device = chan->device; ++ char filename[PATH_MAX]; ++ char dst_addr[18]; ++ char **keys; ++ GKeyFile *key_file; ++ ++ ba2str(device_get_address(device), dst_addr); ++ ++ snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", ++ btd_adapter_get_storage_dir(device_get_adapter(device)), ++ dst_addr); ++ key_file = g_key_file_new(); ++ g_key_file_load_from_file(key_file, filename, 0, NULL); ++ keys = g_key_file_get_keys(key_file, "Endpoints", NULL, NULL); ++ ++ load_remote_sep(chan, key_file, keys); ++ ++ g_strfreev(keys); ++ g_key_file_free(key_file); ++} ++ + static void avdtp_state_cb(struct btd_device *dev, struct avdtp *session, + avdtp_session_state_t old_state, + avdtp_session_state_t new_state, +@@ -1456,6 +1860,9 @@ static void avdtp_state_cb(struct btd_device *dev, struct avdtp *session, + case AVDTP_SESSION_STATE_CONNECTING: + break; + case AVDTP_SESSION_STATE_CONNECTED: ++ if (!chan->session) ++ chan->session = session; ++ load_remote_seps(chan); + break; + } + } +@@ -1904,28 +2311,6 @@ void a2dp_remove_sep(struct a2dp_sep *sep) + a2dp_unregister_sep(sep); + } + +-static void setup_add_caps(struct a2dp_setup *setup, uint8_t *caps, size_t size) +-{ +- struct avdtp_service_capability *media_transport, *media_codec; +- struct avdtp_media_codec_capability *cap; +- +- media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, +- NULL, 0); +- +- setup->caps = g_slist_append(setup->caps, media_transport); +- +- cap = g_malloc0(sizeof(*cap) + size); +- cap->media_type = AVDTP_MEDIA_TYPE_AUDIO; +- cap->media_codec_type = setup->sep->codec; +- memcpy(cap->data, caps, size); +- +- media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, cap, +- sizeof(*cap) + size); +- +- setup->caps = g_slist_append(setup->caps, media_codec); +- g_free(cap); +-} +- + static void select_cb(struct a2dp_setup *setup, void *ret, int size) + { + if (size < 0) { +@@ -1933,7 +2318,7 @@ static void select_cb(struct a2dp_setup *setup, void *ret, int size) + goto done; + } + +- setup_add_caps(setup, ret, size); ++ caps_add_codec(&setup->caps, setup->sep->codec, ret, size); + + done: + finalize_select(setup); +@@ -1989,275 +2374,58 @@ static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type, + return a2dp_find_sep(session, l, NULL); + } + +-struct client { +- const char *sender; +- const char *path; +-}; +- +-static int match_client(const void *data, const void *user_data) +-{ +- struct a2dp_sep *sep = (void *) data; +- const struct a2dp_endpoint *endpoint = sep->endpoint; +- const struct client *client = user_data; +- +- if (strcmp(client->sender, endpoint->get_name(sep, sep->user_data))) +- return -1; +- +- return strcmp(client->path, endpoint->get_path(sep, sep->user_data)); +-} +- +-static struct a2dp_sep *find_sep(struct a2dp_server *server, const char *sender, +- const char *path) +-{ +- GSList *l; +- struct client client = { sender, path }; +- +- l = g_slist_find_custom(server->sources, &client, match_client); +- if (l) +- return l->data; +- +- l = g_slist_find_custom(server->sinks, &client, match_client); +- if (l) +- return l->data; +- +- return NULL; +-} +- +-static int parse_properties(DBusMessageIter *props, uint8_t **caps, int *size) +-{ +- while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { +- const char *key; +- DBusMessageIter value, entry; +- int var; +- +- dbus_message_iter_recurse(props, &entry); +- dbus_message_iter_get_basic(&entry, &key); +- +- dbus_message_iter_next(&entry); +- dbus_message_iter_recurse(&entry, &value); +- +- var = dbus_message_iter_get_arg_type(&value); +- if (strcasecmp(key, "Capabilities") == 0) { +- DBusMessageIter array; +- +- if (var != DBUS_TYPE_ARRAY) +- return -EINVAL; +- +- dbus_message_iter_recurse(&value, &array); +- dbus_message_iter_get_fixed_array(&array, caps, size); +- return 0; +- } +- +- dbus_message_iter_next(props); +- } +- +- return -EINVAL; +-} +- +-static int a2dp_reconfig(struct a2dp_channel *chan, const char *sender, +- struct a2dp_sep *lsep, struct a2dp_remote_sep *rsep, +- uint8_t *caps, int size) +-{ +- struct a2dp_setup *setup; +- const struct queue_entry *entry; +- int err; +- +- setup = a2dp_setup_get(chan->session); +- if (!setup) +- return -ENOMEM; +- +- setup->sep = lsep; +- setup->rsep = rsep; +- +- setup_add_caps(setup, caps, size); +- +- /* Check for existing stream and close it */ +- for (entry = queue_get_entries(chan->server->seps); entry; +- entry = entry->next) { +- struct a2dp_sep *tmp = entry->data; +- +- /* Attempt to reconfigure if a stream already exists */ +- if (tmp->stream) { +- /* Only allow switching sep from the same sender */ +- if (strcmp(sender, tmp->endpoint->get_name(tmp, +- tmp->user_data))) +- return -EPERM; +- +- err = avdtp_close(chan->session, tmp->stream, FALSE); +- if (err < 0) { +- error("avdtp_close: %s", strerror(-err)); +- return err; +- } +- +- setup->reconfigure = TRUE; +- +- return 0; +- } +- } +- +- err = avdtp_set_configuration(setup->session, setup->rsep->sep, +- lsep->lsep, +- setup->caps, +- &setup->stream); +- if (err < 0) { +- error("avdtp_set_configuration: %s", strerror(-err)); +- return err; +- } +- +- return 0; +-} +- +-static DBusMessage *set_configuration(DBusConnection *conn, DBusMessage *msg, +- void *data) +-{ +- struct a2dp_remote_sep *rsep = data; +- struct a2dp_channel *chan = rsep->chan; +- struct a2dp_sep *lsep; +- struct avdtp_service_capability *service; +- struct avdtp_media_codec_capability *codec; +- DBusMessageIter args, props; +- const char *sender, *path; +- uint8_t *caps; +- int err, size = 0; +- +- sender = dbus_message_get_sender(msg); +- +- dbus_message_iter_init(msg, &args); +- +- dbus_message_iter_get_basic(&args, &path); +- dbus_message_iter_next(&args); +- +- lsep = find_sep(chan->server, sender, path); +- if (!lsep) +- return btd_error_invalid_args(msg); +- +- /* Check if SEPs are no the same role */ +- if (avdtp_get_type(rsep->sep) == lsep->type) +- return btd_error_invalid_args(msg); +- +- service = avdtp_get_codec(rsep->sep); +- codec = (struct avdtp_media_codec_capability *) service->data; +- +- /* Check if codec match */ +- if (!endpoint_match_codec_ind(chan->session, codec, lsep)) +- return btd_error_invalid_args(msg); +- +- dbus_message_iter_recurse(&args, &props); +- if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY) +- return btd_error_invalid_args(msg); +- +- if (parse_properties(&props, &caps, &size) < 0) +- return btd_error_invalid_args(msg); +- +- err = a2dp_reconfig(chan, sender, lsep, rsep, caps, size); +- if (err < 0) +- return btd_error_failed(msg, strerror(-err)); +- +- return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +-} +- +-static const GDBusMethodTable sep_methods[] = { +- { GDBUS_EXPERIMENTAL_ASYNC_METHOD("SetConfiguration", +- GDBUS_ARGS({ "endpoint", "o" }, +- { "properties", "a{sv}" } ), +- NULL, set_configuration) }, +- { }, +-}; +- +-static gboolean get_uuid(const GDBusPropertyTable *property, +- DBusMessageIter *iter, void *data) +-{ +- struct a2dp_remote_sep *sep = data; +- const char *uuid; +- +- switch (avdtp_get_type(sep->sep)) { +- case AVDTP_SEP_TYPE_SOURCE: +- uuid = A2DP_SOURCE_UUID; +- break; +- case AVDTP_SEP_TYPE_SINK: +- uuid = A2DP_SOURCE_UUID; +- break; +- default: +- uuid = ""; +- } +- +- dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &uuid); +- +- return TRUE; +-} +- +-static gboolean get_codec(const GDBusPropertyTable *property, +- DBusMessageIter *iter, void *data) +-{ +- struct a2dp_remote_sep *sep = data; +- struct avdtp_service_capability *cap = avdtp_get_codec(sep->sep); +- struct avdtp_media_codec_capability *codec = (void *) cap->data; +- +- dbus_message_iter_append_basic(iter, DBUS_TYPE_BYTE, +- &codec->media_codec_type); +- +- return TRUE; +-} +- +-static gboolean get_capabilities(const GDBusPropertyTable *property, +- DBusMessageIter *iter, void *data) ++static void store_remote_sep(void *data, void *user_data) + { + struct a2dp_remote_sep *sep = data; ++ GKeyFile *key_file = (void *) user_data; ++ char seid[4], value[256]; + struct avdtp_service_capability *service = avdtp_get_codec(sep->sep); + struct avdtp_media_codec_capability *codec = (void *) service->data; +- uint8_t *caps = codec->data; +- DBusMessageIter array; ++ unsigned int i; ++ ssize_t offset; + +- dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, +- DBUS_TYPE_BYTE_AS_STRING, &array); ++ sprintf(seid, "%02hhx", avdtp_get_seid(sep->sep)); + +- dbus_message_iter_append_fixed_array(&array, DBUS_TYPE_BYTE, &caps, +- service->length - sizeof(*codec)); ++ offset = sprintf(value, "%02hhx:%02hhx:", avdtp_get_type(sep->sep), ++ codec->media_codec_type); + +- dbus_message_iter_close_container(iter, &array); ++ for (i = 0; i < service->length - sizeof(*codec); i++) ++ offset += sprintf(value + offset, "%02hhx", codec->data[i]); + +- return TRUE; +-} + +-static const GDBusPropertyTable sep_properties[] = { +- { "UUID", "s", get_uuid, NULL, NULL, +- G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, +- { "Codec", "y", get_codec, NULL, NULL, +- G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, +- { "Capabilities", "ay", get_capabilities, NULL, NULL, +- G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, +- { } +-}; ++ g_key_file_set_string(key_file, "Endpoints", seid, value); ++} + +-static void register_remote_sep(void *data, void *user_data) ++static void store_remote_seps(struct a2dp_channel *chan) + { +- struct avdtp_remote_sep *rsep = data; +- struct a2dp_setup *setup = user_data; +- struct a2dp_remote_sep *sep; ++ struct btd_device *device = chan->device; ++ char filename[PATH_MAX]; ++ char dst_addr[18]; ++ GKeyFile *key_file; ++ char *data; ++ gsize length = 0; + +- sep = new0(struct a2dp_remote_sep, 1); +- sep->chan = setup->chan; +- sep->sep = rsep; ++ if (queue_isempty(chan->seps)) ++ return; + +- if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) +- goto done; ++ ba2str(device_get_address(device), dst_addr); + +- asprintf(&sep->path, "%s/sep%d", device_get_path(setup->chan->device), +- avdtp_get_seid(rsep)); ++ snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", ++ btd_adapter_get_storage_dir(device_get_adapter(device)), ++ dst_addr); ++ key_file = g_key_file_new(); ++ g_key_file_load_from_file(key_file, filename, 0, NULL); + +- if (g_dbus_register_interface(btd_get_dbus_connection(), +- sep->path, MEDIA_ENDPOINT_INTERFACE, +- sep_methods, NULL, sep_properties, +- sep, remote_sep_free) == FALSE) { +- error("Could not register remote sep %s", sep->path); +- free(sep->path); +- sep->path = NULL; +- } ++ /* Remove current endpoints since it might have changed */ ++ g_key_file_remove_group(key_file, "Endpoints", NULL); + +- DBG("Found remote SEP: %s", sep->path); ++ queue_foreach(chan->seps, store_remote_sep, key_file); + +-done: +- queue_push_tail(setup->chan->seps, sep); ++ data = g_key_file_to_data(key_file, &length, NULL); ++ g_file_set_contents(filename, data, length, NULL); ++ ++ g_free(data); ++ g_key_file_free(key_file); + } + + static void discover_cb(struct avdtp *session, GSList *seps, +@@ -2270,8 +2438,10 @@ static void discover_cb(struct avdtp *session, GSList *seps, + setup->seps = seps; + setup->err = err; + +- if (!err && queue_isempty(setup->chan->seps)) +- g_slist_foreach(seps, register_remote_sep, setup); ++ if (!err) { ++ g_slist_foreach(seps, register_remote_sep, setup->chan); ++ store_remote_seps(setup->chan); ++ } + + finalize_discover(setup); + } +diff --git a/profiles/audio/avdtp.c b/profiles/audio/avdtp.c +index cc4322d10..4f964feb5 100644 +--- a/profiles/audio/avdtp.c ++++ b/profiles/audio/avdtp.c +@@ -2638,12 +2638,15 @@ static gboolean avdtp_discover_resp(struct avdtp *session, + stream = find_stream_by_rseid(session, resp->seps[i].seid); + + sep = find_remote_sep(session->seps, resp->seps[i].seid); +- if (!sep) { +- if (resp->seps[i].inuse && !stream) +- continue; +- sep = g_new0(struct avdtp_remote_sep, 1); +- session->seps = g_slist_append(session->seps, sep); +- } ++ if (sep && sep->type == resp->seps[i].type && ++ sep->media_type == resp->seps[i].media_type) ++ continue; ++ ++ if (resp->seps[i].inuse && !stream) ++ continue; ++ ++ sep = g_new0(struct avdtp_remote_sep, 1); ++ session->seps = g_slist_append(session->seps, sep); + + sep->stream = stream; + sep->seid = resp->seps[i].seid; +@@ -3192,6 +3195,37 @@ struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + return cap; + } + ++struct avdtp_remote_sep *avdtp_register_remote_sep(struct avdtp *session, ++ uint8_t seid, ++ uint8_t type, ++ GSList *caps) ++{ ++ struct avdtp_remote_sep *sep; ++ GSList *l; ++ ++ sep = find_remote_sep(session->seps, seid); ++ if (sep) ++ return sep; ++ ++ sep = g_new0(struct avdtp_remote_sep, 1); ++ session->seps = g_slist_append(session->seps, sep); ++ sep->seid = seid; ++ sep->type = type; ++ sep->media_type = AVDTP_MEDIA_TYPE_AUDIO; ++ sep->caps = caps; ++ ++ for (l = caps; l; l = g_slist_next(l)) { ++ struct avdtp_service_capability *cap = l->data; ++ ++ if (cap->category == AVDTP_MEDIA_CODEC) ++ sep->codec = cap; ++ } ++ ++ DBG("seid %d type %d media %d", sep->seid, sep->type, sep->media_type); ++ ++ return sep; ++} ++ + static gboolean process_discover(gpointer data) + { + struct avdtp *session = data; +diff --git a/profiles/audio/avdtp.h b/profiles/audio/avdtp.h +index e5fc40c89..b03ca9030 100644 +--- a/profiles/audio/avdtp.h ++++ b/profiles/audio/avdtp.h +@@ -223,6 +223,11 @@ struct avdtp *avdtp_ref(struct avdtp *session); + struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category, + void *data, int size); + ++struct avdtp_remote_sep *avdtp_register_remote_sep(struct avdtp *session, ++ uint8_t seid, ++ uint8_t type, ++ GSList *caps); ++ + uint8_t avdtp_get_seid(struct avdtp_remote_sep *sep); + + uint8_t avdtp_get_type(struct avdtp_remote_sep *sep); +-- +2.21.0 + + +From 50d87b05b3322f3678b6dda97e4c26ef186f63ac Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Tue, 15 Jan 2019 11:06:04 -0300 +Subject: [PATCH 16/31] doc/media-api: Add Device property to MediaEndpoint + +This adds Device property which indicates which device the endpoint +belongs to. +--- + doc/media-api.txt | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/doc/media-api.txt b/doc/media-api.txt +index 93c3490b6..b909d8e73 100644 +--- a/doc/media-api.txt ++++ b/doc/media-api.txt +@@ -533,6 +533,10 @@ Methods void SetConfiguration(object transport, dict properties) + already been unregistered. + + ++ object Device [readonly, optional]: ++ ++ Device object which the endpoint is belongs to. ++ + MediaTransport1 hierarchy + ========================= + +-- +2.21.0 + + +From 394b06e7f706de581a4aa50d20232081bceaa824 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Tue, 15 Jan 2019 11:07:13 -0300 +Subject: [PATCH 17/31] a2dp: Add implementation of MediaEndpoint.Device + +This adds the implementation of MediaEndpoint.Device property so the +clints don't need to guess what device the endpoint belongs. +--- + profiles/audio/a2dp.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 6975682c9..ff384cd23 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -1718,6 +1718,19 @@ static gboolean get_capabilities(const GDBusPropertyTable *property, + return TRUE; + } + ++static gboolean get_device(const GDBusPropertyTable *property, ++ DBusMessageIter *iter, void *data) ++{ ++ struct a2dp_remote_sep *sep = data; ++ const char *path; ++ ++ path = device_get_path(sep->chan->device); ++ ++ dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); ++ ++ return TRUE; ++} ++ + static const GDBusPropertyTable sep_properties[] = { + { "UUID", "s", get_uuid, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, +@@ -1725,6 +1738,8 @@ static const GDBusPropertyTable sep_properties[] = { + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { "Capabilities", "ay", get_capabilities, NULL, NULL, + G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, ++ { "Device", "o", get_device, NULL, NULL, ++ G_DBUS_PROPERTY_FLAG_EXPERIMENTAL }, + { } + }; + +-- +2.21.0 + + +From 4222cbdd5909043c7f903bbca9bb377ba7aeb7bb Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Tue, 22 Jan 2019 15:28:12 +0200 +Subject: [PATCH 18/31] a2dp: Add reverse discovery + +Now that remote endpoints are exposed there is a chance that a +configured device will reconnect and initiate SetConfiguration skipping +the discovery phase which is now required in order to be able to switch +endpoints, so this introduces the reverse discovery logic in order to +find out about remote endpoints capabilities if they have not been +found yet. +--- + profiles/audio/a2dp.c | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index ff384cd23..4001ea0ea 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -583,6 +583,12 @@ static gboolean endpoint_match_codec_ind(struct avdtp *session, + return TRUE; + } + ++static void reverse_discover(struct avdtp *session, GSList *seps, int err, ++ void *user_data) ++{ ++ DBG("err %d", err); ++} ++ + static gboolean endpoint_setconf_ind(struct avdtp *session, + struct avdtp_local_sep *sep, + struct avdtp_stream *stream, +@@ -638,8 +644,14 @@ static gboolean endpoint_setconf_ind(struct avdtp *session, + setup_ref(setup), + endpoint_setconf_cb, + a2dp_sep->user_data); +- if (ret == 0) ++ if (ret == 0) { ++ /* Attempt to reverve discover if there are no remote ++ * SEPs. ++ */ ++ if (queue_isempty(setup->chan->seps)) ++ a2dp_discover(session, reverse_discover, NULL); + return TRUE; ++ } + + setup_unref(setup); + setup->err = g_new(struct avdtp_error, 1); +-- +2.21.0 + + +From 18495cf34e6412af10da2632289cf0c07b5e23ab Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sat, 26 Jan 2019 11:46:06 +0100 +Subject: [PATCH 19/31] a2dp-codecs & avinfo: Simplify defintions and parsing + of aptX family + +Reuse whole a2dp_aptx_t structure and defines as they are same for aptX Low +Latency and aptX HD. +--- + profiles/audio/a2dp-codecs.h | 46 +++++-------------------------- + tools/avinfo.c | 53 ++++++++++-------------------------- + 2 files changed, 22 insertions(+), 77 deletions(-) + +diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h +index 0bdd29110..16088dc26 100644 +--- a/profiles/audio/a2dp-codecs.h ++++ b/profiles/audio/a2dp-codecs.h +@@ -212,14 +212,6 @@ + #define APTX_LL_VENDOR_ID 0x0000000a + #define APTX_LL_CODEC_ID 0x0002 + +-#define APTX_LL_CHANNEL_MODE_MONO 0x01 +-#define APTX_LL_CHANNEL_MODE_STEREO 0x02 +- +-#define APTX_LL_SAMPLING_FREQ_16000 0x08 +-#define APTX_LL_SAMPLING_FREQ_32000 0x04 +-#define APTX_LL_SAMPLING_FREQ_44100 0x02 +-#define APTX_LL_SAMPLING_FREQ_48000 0x01 +- + /* Default parameters for aptX Low Latency encoder */ + + /* Target codec buffer level = 180 */ +@@ -243,14 +235,6 @@ + #define APTX_HD_VENDOR_ID 0x000000D7 + #define APTX_HD_CODEC_ID 0x0024 + +-#define APTX_HD_CHANNEL_MODE_MONO 0x1 +-#define APTX_HD_CHANNEL_MODE_STEREO 0x2 +- +-#define APTX_HD_SAMPLING_FREQ_16000 0x8 +-#define APTX_HD_SAMPLING_FREQ_32000 0x4 +-#define APTX_HD_SAMPLING_FREQ_44100 0x2 +-#define APTX_HD_SAMPLING_FREQ_48000 0x1 +- + #define LDAC_VENDOR_ID 0x0000012d + #define LDAC_CODEC_ID 0x00aa + +@@ -359,25 +343,13 @@ typedef struct { + } __attribute__ ((packed)) a2dp_faststream_t; + + typedef struct { +- a2dp_vendor_codec_t info; +- uint8_t channel_mode:4; +- uint8_t frequency:4; ++ a2dp_aptx_t aptx; + uint8_t bidirect_link:1; + uint8_t has_new_caps:1; + uint8_t reserved:6; + a2dp_aptx_ll_new_caps_t new_caps[0]; + } __attribute__ ((packed)) a2dp_aptx_ll_t; + +-typedef struct { +- a2dp_vendor_codec_t info; +- uint8_t channel_mode:4; +- uint8_t frequency:4; +- uint8_t reserved0; +- uint8_t reserved1; +- uint8_t reserved2; +- uint8_t reserved3; +-} __attribute__ ((packed)) a2dp_aptx_hd_t; +- + #elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN) && \ + __BYTE_ORDER == __BIG_ENDIAN + +@@ -429,25 +401,21 @@ typedef struct { + } __attribute__ ((packed)) a2dp_faststream_t; + + typedef struct { +- a2dp_vendor_codec_t info; +- uint8_t frequency:4; +- uint8_t channel_mode:4; ++ a2dp_aptx_t aptx; + uint8_t reserved:6; + uint8_t has_new_caps:1; + uint8_t bidirect_link:1; + a2dp_aptx_ll_new_caps_t new_caps[0]; + } __attribute__ ((packed)) a2dp_aptx_ll_t; + ++#else ++#error "Unknown byte order" ++#endif ++ + typedef struct { +- a2dp_vendor_codec_t info; +- uint8_t frequency:4; +- uint8_t channel_mode:4; ++ a2dp_aptx_t aptx; + uint8_t reserved0; + uint8_t reserved1; + uint8_t reserved2; + uint8_t reserved3; + } __attribute__ ((packed)) a2dp_aptx_hd_t; +- +-#else +-#error "Unknown byte order" +-#endif +diff --git a/tools/avinfo.c b/tools/avinfo.c +index 02fc1f233..ea7a93ed2 100644 +--- a/tools/avinfo.c ++++ b/tools/avinfo.c +@@ -168,15 +168,8 @@ struct avdtp_content_protection_capability { + uint8_t data[0]; + } __attribute__ ((packed)); + +-static void print_aptx(a2dp_aptx_t *aptx, uint8_t size) ++static void print_aptx_common(a2dp_aptx_t *aptx) + { +- printf("\t\tVendor Specific Value (aptX)"); +- +- if (size < sizeof(*aptx)) { +- printf(" (broken)\n"); +- return; +- } +- + printf("\n\t\t\tFrequencies: "); + if (aptx->frequency & APTX_SAMPLING_FREQ_16000) + printf("16kHz "); +@@ -192,6 +185,18 @@ static void print_aptx(a2dp_aptx_t *aptx, uint8_t size) + printf("Mono "); + if (aptx->channel_mode & APTX_CHANNEL_MODE_STEREO) + printf("Stereo "); ++} ++ ++static void print_aptx(a2dp_aptx_t *aptx, uint8_t size) ++{ ++ printf("\t\tVendor Specific Value (aptX)"); ++ ++ if (size < sizeof(*aptx)) { ++ printf(" (broken)\n"); ++ return; ++ } ++ ++ print_aptx_common(aptx); + + printf("\n"); + } +@@ -242,21 +247,7 @@ static void print_aptx_ll(a2dp_aptx_ll_t *aptx_ll, uint8_t size) + return; + } + +- printf("\n\t\t\tFrequencies: "); +- if (aptx_ll->frequency & APTX_LL_SAMPLING_FREQ_16000) +- printf("16kHz "); +- if (aptx_ll->frequency & APTX_LL_SAMPLING_FREQ_32000) +- printf("32kHz "); +- if (aptx_ll->frequency & APTX_LL_SAMPLING_FREQ_44100) +- printf("44.1kHz "); +- if (aptx_ll->frequency & APTX_LL_SAMPLING_FREQ_48000) +- printf("48kHz "); +- +- printf("\n\t\t\tChannel modes: "); +- if (aptx_ll->channel_mode & APTX_LL_CHANNEL_MODE_MONO) +- printf("Mono "); +- if (aptx_ll->channel_mode & APTX_LL_CHANNEL_MODE_STEREO) +- printf("Stereo "); ++ print_aptx_common(&aptx_ll->aptx); + + printf("\n\t\tBidirectional link: %s", + aptx_ll->bidirect_link ? "Yes" : "No"); +@@ -292,21 +283,7 @@ static void print_aptx_hd(a2dp_aptx_hd_t *aptx_hd, uint8_t size) + return; + } + +- printf("\n\t\t\tFrequencies: "); +- if (aptx_hd->frequency & APTX_HD_SAMPLING_FREQ_16000) +- printf("16kHz "); +- if (aptx_hd->frequency & APTX_HD_SAMPLING_FREQ_32000) +- printf("32kHz "); +- if (aptx_hd->frequency & APTX_HD_SAMPLING_FREQ_44100) +- printf("44.1kHz "); +- if (aptx_hd->frequency & APTX_HD_SAMPLING_FREQ_48000) +- printf("48kHz "); +- +- printf("\n\t\t\tChannel modes: "); +- if (aptx_hd->channel_mode & APTX_HD_CHANNEL_MODE_MONO) +- printf("Mono "); +- if (aptx_hd->channel_mode & APTX_HD_CHANNEL_MODE_STEREO) +- printf("Stereo "); ++ print_aptx_common(&aptx_hd->aptx); + + printf("\n"); + } +-- +2.21.0 + + +From 5861b20c6bd9c4f82c7b20de1101d9eebc90345b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sat, 26 Jan 2019 11:24:50 +0100 +Subject: [PATCH 20/31] avinfo: Dump unknown codecs and unknown categories + +--- + tools/avinfo.c | 15 ++++++++++++++- + 1 file changed, 14 insertions(+), 1 deletion(-) + +diff --git a/tools/avinfo.c b/tools/avinfo.c +index ea7a93ed2..6f9f2763b 100644 +--- a/tools/avinfo.c ++++ b/tools/avinfo.c +@@ -625,6 +625,8 @@ static void print_media_codec( + struct avdtp_media_codec_capability *cap, + uint8_t size) + { ++ int i; ++ + if (size < sizeof(*cap)) { + printf("\tMedia Codec: Unknown (broken)\n"); + return; +@@ -645,6 +647,10 @@ static void print_media_codec( + break; + default: + printf("\tMedia Codec: Unknown\n"); ++ printf("\t\tCodec Data:"); ++ for (i = 0; i < size - 2; ++i) ++ printf(" 0x%.02x", ((unsigned char *)cap->data)[i]); ++ printf("\n"); + } + } + +@@ -676,6 +682,7 @@ static void print_content_protection( + static void print_caps(void *data, int size) + { + int processed; ++ int i; + + for (processed = 0; processed + 2 < size;) { + struct avdtp_service_capability *cap; +@@ -692,9 +699,15 @@ static void print_caps(void *data, int size) + case AVDTP_REPORTING: + case AVDTP_RECOVERY: + case AVDTP_MULTIPLEXING: +- default: + /* FIXME: Add proper functions */ ++ break; ++ default: + printf("\tUnknown category: %d\n", cap->category); ++ printf("\t\tData:"); ++ for (i = 0; i < cap->length; ++i) ++ printf(" 0x%.02x", ++ ((unsigned char *)cap->data)[i]); ++ printf("\n"); + break; + case AVDTP_MEDIA_CODEC: + print_media_codec((void *) cap->data, cap->length); +-- +2.21.0 + + +From e6ad4cbe20f3e66c77fa27c2d8b721a4ccdef58f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Sat, 26 Jan 2019 11:24:15 +0100 +Subject: [PATCH 21/31] avinfo: Fix parsing capabilities + +Function print_caps() expects capabilities buffer without AVDTP header. +Previously avinfo somehow worked, because AVDTP header looks like +capability header with unknown category which was skipped. +--- + tools/avinfo.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/avinfo.c b/tools/avinfo.c +index 6f9f2763b..e45b50918 100644 +--- a/tools/avinfo.c ++++ b/tools/avinfo.c +@@ -799,7 +799,7 @@ static ssize_t avdtp_get_caps(int sk, int seid) + return -1; + } + +- print_caps(caps, ret); ++ print_caps(caps->caps, ret - sizeof(struct getcap_resp)); + + return 0; + } +-- +2.21.0 + + +From 87805eea93c451aa1b438a2b01967a96f53dce1f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Pali=20Roh=C3=A1r?= +Date: Tue, 29 Jan 2019 18:12:07 +0100 +Subject: [PATCH 22/31] a2dp-codecs: Fix SBC_MAX_BITPOOL and add SBC quality + modes + +According to A2DP specification; section SBC; Codec Specific Information +Elements; Minimum / Maximum Bitpool Value, range for Bitpool value is from +2 to 250. + +A2DP specification also defines bitpool values for two SBC modes: Middle +Quality and High Quality. They depends on channel mode and frequency. So +add definitions for them into a2dp-codecs file too. + +File android/hal-audio-sbc.c was updated to use High Quality mode for +chosen frequency. +--- + android/hal-audio-sbc.c | 6 +++--- + profiles/audio/a2dp-codecs.h | 16 +++++++++++++++- + 2 files changed, 18 insertions(+), 4 deletions(-) + +diff --git a/android/hal-audio-sbc.c b/android/hal-audio-sbc.c +index fd6c61b95..4df4ec7ff 100644 +--- a/android/hal-audio-sbc.c ++++ b/android/hal-audio-sbc.c +@@ -91,7 +91,7 @@ static const a2dp_sbc_t sbc_presets[] = { + .block_length = SBC_BLOCK_LENGTH_4 | SBC_BLOCK_LENGTH_8 | + SBC_BLOCK_LENGTH_12 | SBC_BLOCK_LENGTH_16, + .min_bitpool = SBC_MIN_BITPOOL, +- .max_bitpool = SBC_MAX_BITPOOL ++ .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_44100, + }, + { + .frequency = SBC_SAMPLING_FREQ_44100, +@@ -100,7 +100,7 @@ static const a2dp_sbc_t sbc_presets[] = { + .allocation_method = SBC_ALLOCATION_LOUDNESS, + .block_length = SBC_BLOCK_LENGTH_16, + .min_bitpool = SBC_MIN_BITPOOL, +- .max_bitpool = SBC_MAX_BITPOOL ++ .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_44100, + }, + { + .frequency = SBC_SAMPLING_FREQ_48000, +@@ -109,7 +109,7 @@ static const a2dp_sbc_t sbc_presets[] = { + .allocation_method = SBC_ALLOCATION_LOUDNESS, + .block_length = SBC_BLOCK_LENGTH_16, + .min_bitpool = SBC_MIN_BITPOOL, +- .max_bitpool = SBC_MAX_BITPOOL ++ .max_bitpool = SBC_BITPOOL_HQ_JOINT_STEREO_48000, + }, + }; + +diff --git a/profiles/audio/a2dp-codecs.h b/profiles/audio/a2dp-codecs.h +index 16088dc26..93e9d3523 100644 +--- a/profiles/audio/a2dp-codecs.h ++++ b/profiles/audio/a2dp-codecs.h +@@ -53,8 +53,22 @@ + #define SBC_ALLOCATION_SNR (1 << 1) + #define SBC_ALLOCATION_LOUDNESS 1 + +-#define SBC_MAX_BITPOOL 64 + #define SBC_MIN_BITPOOL 2 ++#define SBC_MAX_BITPOOL 250 ++ ++/* Other settings: ++ * Block length = 16 ++ * Allocation method = Loudness ++ * Subbands = 8 ++ */ ++#define SBC_BITPOOL_MQ_MONO_44100 19 ++#define SBC_BITPOOL_MQ_MONO_48000 18 ++#define SBC_BITPOOL_MQ_JOINT_STEREO_44100 35 ++#define SBC_BITPOOL_MQ_JOINT_STEREO_48000 33 ++#define SBC_BITPOOL_HQ_MONO_44100 31 ++#define SBC_BITPOOL_HQ_MONO_48000 29 ++#define SBC_BITPOOL_HQ_JOINT_STEREO_44100 53 ++#define SBC_BITPOOL_HQ_JOINT_STEREO_48000 51 + + #define MPEG_CHANNEL_MODE_MONO (1 << 3) + #define MPEG_CHANNEL_MODE_DUAL_CHANNEL (1 << 2) +-- +2.21.0 + + +From d23b245146678847d4afa87d8ea9d859935a5a37 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Mon, 8 Apr 2019 12:41:39 +0300 +Subject: [PATCH 23/31] a2dp: Fix UUID of remote Sinks + +Sinks were being reported as Sources. +--- + profiles/audio/a2dp.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 4001ea0ea..8f141739c 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -1686,7 +1686,7 @@ static gboolean get_uuid(const GDBusPropertyTable *property, + uuid = A2DP_SOURCE_UUID; + break; + case AVDTP_SEP_TYPE_SINK: +- uuid = A2DP_SOURCE_UUID; ++ uuid = A2DP_SINK_UUID; + break; + default: + uuid = ""; +-- +2.21.0 + + +From 44257aa797796ac3ddc0e01de68df596b6ebf858 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 3 May 2019 14:49:22 +0300 +Subject: [PATCH 24/31] a2dp: Fix useless statement + +Checking for NULL path doesn't really matter since NULL is returned +anyway. +--- + profiles/audio/a2dp.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 8f141739c..1c9473887 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -2873,8 +2873,7 @@ struct btd_device *a2dp_setup_get_device(struct a2dp_setup *setup) + const char *a2dp_setup_remote_path(struct a2dp_setup *setup) + { + if (setup->rsep) { +- if (setup->rsep->path) +- return setup->rsep->path; ++ return setup->rsep->path; + } + + return NULL; +-- +2.21.0 + + +From 12e91430886546160f6131c1622a4923adedf3d2 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Tue, 23 Apr 2019 18:46:36 +0300 +Subject: [PATCH 25/31] a2dp: Store last used endpoint + +This stores the last used endpoint whenever it is considered open and +then prefer to use it when attempting to reconnect. +--- + profiles/audio/a2dp.c | 105 ++++++++++++++++++++++++++++++++++++------ + 1 file changed, 92 insertions(+), 13 deletions(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 1c9473887..d1ed82243 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -147,6 +147,7 @@ struct a2dp_channel { + unsigned int auth_id; + struct avdtp *session; + struct queue *seps; ++ struct a2dp_remote_sep *last_used; + }; + + static GSList *servers = NULL; +@@ -860,6 +861,60 @@ static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep, + return TRUE; + } + ++static bool match_remote_sep(const void *data, const void *user_data) ++{ ++ const struct a2dp_remote_sep *sep = data; ++ const struct avdtp_remote_sep *rsep = user_data; ++ ++ return sep->sep == rsep; ++} ++ ++static void store_last_used(struct a2dp_channel *chan, ++ struct avdtp_remote_sep *rsep) ++{ ++ GKeyFile *key_file; ++ char filename[PATH_MAX]; ++ char dst_addr[18]; ++ char value[4]; ++ char *data; ++ size_t len = 0; ++ ++ ba2str(device_get_address(chan->device), dst_addr); ++ ++ snprintf(filename, PATH_MAX, STORAGEDIR "/%s/cache/%s", ++ btd_adapter_get_storage_dir(device_get_adapter(chan->device)), ++ dst_addr); ++ key_file = g_key_file_new(); ++ g_key_file_load_from_file(key_file, filename, 0, NULL); ++ ++ sprintf(value, "%02hhx", avdtp_get_seid(rsep)); ++ ++ g_key_file_set_string(key_file, "Endpoints", "LastUsed", value); ++ ++ data = g_key_file_to_data(key_file, &len, NULL); ++ g_file_set_contents(filename, data, len, NULL); ++ ++ g_free(data); ++ g_key_file_free(key_file); ++} ++ ++static void update_last_used(struct a2dp_channel *chan, ++ struct avdtp_stream *stream) ++{ ++ struct avdtp_remote_sep *rsep; ++ struct a2dp_remote_sep *sep; ++ ++ rsep = avdtp_stream_get_remote_sep(stream); ++ ++ /* Update last used */ ++ sep = queue_find(chan->seps, match_remote_sep, rsep); ++ if (chan->last_used == sep) ++ return; ++ ++ chan->last_used = sep; ++ store_last_used(chan, rsep); ++} ++ + static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +@@ -884,7 +939,8 @@ static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + setup->err = err; + if (setup->start) + finalize_resume(setup); +- } ++ } else if (setup->chan) ++ update_last_used(setup->chan, stream); + + finalize_config(setup); + +@@ -1077,14 +1133,6 @@ static gboolean close_ind(struct avdtp *session, struct avdtp_local_sep *sep, + return TRUE; + } + +-static bool match_remote_sep(const void *data, const void *user_data) +-{ +- const struct a2dp_remote_sep *sep = data; +- const struct avdtp_remote_sep *rsep = user_data; +- +- return sep->sep == rsep; +-} +- + static struct a2dp_remote_sep *find_remote_sep(struct a2dp_channel *chan, + struct a2dp_sep *sep) + { +@@ -1791,19 +1839,28 @@ done: + queue_push_tail(chan->seps, sep); + } + ++static bool match_seid(const void *data, const void *user_data) ++{ ++ const struct a2dp_remote_sep *sep = data; ++ const uint8_t *seid = user_data; ++ ++ return avdtp_get_seid(sep->sep) == *seid; ++} ++ + static void load_remote_sep(struct a2dp_channel *chan, GKeyFile *key_file, + char **seids) + { + struct avdtp_remote_sep *sep; ++ uint8_t seid; ++ char *value; + + if (!seids) + return; + + for (; *seids; seids++) { +- uint8_t seid; + uint8_t type; + uint8_t codec; +- char *value, caps[256]; ++ char caps[256]; + uint8_t data[128]; + int i, size; + GSList *l = NULL; +@@ -1847,6 +1904,15 @@ static void load_remote_sep(struct a2dp_channel *chan, GKeyFile *key_file, + + register_remote_sep(sep, chan); + } ++ ++ value = g_key_file_get_string(key_file, "Endpoints", "LastUsed", NULL); ++ if (!value) ++ return; ++ ++ if (sscanf(value, "%02hhx", &seid) != 1) ++ return; ++ ++ chan->last_used = queue_find(chan->seps, match_seid, &seid); + } + + static void load_remote_seps(struct a2dp_channel *chan) +@@ -2355,8 +2421,12 @@ done: + static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list, + const char *sender) + { ++ struct a2dp_sep *first = NULL; ++ struct a2dp_channel *chan = find_channel(session); ++ + for (; list; list = list->next) { + struct a2dp_sep *sep = list->data; ++ struct avdtp_remote_sep *rsep; + + /* Use sender's endpoint if available */ + if (sender) { +@@ -2370,14 +2440,23 @@ static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list, + continue; + } + +- if (avdtp_find_remote_sep(session, sep->lsep) == NULL) ++ rsep = avdtp_find_remote_sep(session, sep->lsep); ++ if (!rsep) + continue; + ++ /* Locate last used if set */ ++ if (chan->last_used) { ++ if (chan->last_used->sep == rsep) ++ return sep; ++ first = sep; ++ continue; ++ } ++ + return sep; + + } + +- return NULL; ++ return first; + } + + static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type, +-- +2.21.0 + + +From 2934144ad10e7d6988cbfd4e3642be275d41c450 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Mon, 29 Apr 2019 13:30:34 +0300 +Subject: [PATCH 26/31] a2dp: Fix not calling SelectConfiguration on other + available endpoints + +Endpoint may not be able to select a valid configuration but there could +be other endpoints available that could be used, so instead of just +using the first match this collects all the matching endpoints and put +them into a queue (ordered by priority) then proceed to next endpoint +only failing if none of the available endpoits can select a valid +configuration. +--- + profiles/audio/a2dp.c | 77 ++++++++++++++++++++++++++++--------------- + 1 file changed, 50 insertions(+), 27 deletions(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index d1ed82243..f10ba6c81 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -105,6 +105,7 @@ struct a2dp_setup_cb { + struct a2dp_setup { + struct a2dp_channel *chan; + struct avdtp *session; ++ struct queue *eps; + struct a2dp_sep *sep; + struct a2dp_remote_sep *rsep; + struct avdtp_stream *stream; +@@ -2406,23 +2407,44 @@ void a2dp_remove_sep(struct a2dp_sep *sep) + + static void select_cb(struct a2dp_setup *setup, void *ret, int size) + { +- if (size < 0) { +- DBG("Endpoint replied an invalid configuration"); ++ struct avdtp_service_capability *service; ++ struct avdtp_media_codec_capability *codec; ++ int err; ++ ++ if (size) { ++ caps_add_codec(&setup->caps, setup->sep->codec, ret, size); + goto done; + } + +- caps_add_codec(&setup->caps, setup->sep->codec, ret, size); ++ setup->sep = queue_pop_head(setup->eps); ++ if (!setup->sep) { ++ error("Unable to select a valid configuration"); ++ queue_destroy(setup->eps, NULL); ++ goto done; ++ } ++ ++ setup->rsep = find_remote_sep(setup->chan, setup->sep); ++ service = avdtp_get_codec(setup->rsep->sep); ++ codec = (struct avdtp_media_codec_capability *) service->data; ++ ++ err = setup->sep->endpoint->select_configuration(setup->sep, ++ codec->data, ++ service->length - sizeof(*codec), ++ setup_ref(setup), ++ select_cb, setup->sep->user_data); ++ if (err == 0) ++ return; + + done: + finalize_select(setup); + setup_unref(setup); + } + +-static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list, ++static struct queue *a2dp_find_eps(struct avdtp *session, GSList *list, + const char *sender) + { +- struct a2dp_sep *first = NULL; + struct a2dp_channel *chan = find_channel(session); ++ struct queue *seps = NULL; + + for (; list; list = list->next) { + struct a2dp_sep *sep = list->data; +@@ -2444,26 +2466,25 @@ static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list, + if (!rsep) + continue; + +- /* Locate last used if set */ +- if (chan->last_used) { +- if (chan->last_used->sep == rsep) +- return sep; +- first = sep; +- continue; +- } ++ if (!seps) ++ seps = queue_new(); + +- return sep; ++ /* Prepend last used so it is preferred over others */ ++ if (chan->last_used && chan->last_used->sep == rsep) ++ queue_push_head(seps, sep); ++ else ++ queue_push_tail(seps, sep); + + } + +- return first; ++ return seps; + } + +-static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type, ++static struct queue *a2dp_select_eps(struct avdtp *session, uint8_t type, + const char *sender) + { + struct a2dp_server *server; +- struct a2dp_sep *sep; ++ struct queue *seps; + GSList *l; + + server = find_server(servers, avdtp_get_adapter(session)); +@@ -2473,11 +2494,11 @@ static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type, + l = type == AVDTP_SEP_TYPE_SINK ? server->sources : server->sinks; + + /* Check sender's seps first */ +- sep = a2dp_find_sep(session, l, sender); +- if (sep != NULL) +- return sep; ++ seps = a2dp_find_eps(session, l, sender); ++ if (seps != NULL) ++ return seps; + +- return a2dp_find_sep(session, l, NULL); ++ return a2dp_find_eps(session, l, NULL); + } + + static void store_remote_sep(void *data, void *user_data) +@@ -2580,13 +2601,13 @@ unsigned int a2dp_select_capabilities(struct avdtp *session, + { + struct a2dp_setup *setup; + struct a2dp_setup_cb *cb_data; +- struct a2dp_sep *sep; ++ struct queue *eps; + struct avdtp_service_capability *service; + struct avdtp_media_codec_capability *codec; + int err; + +- sep = a2dp_select_sep(session, type, sender); +- if (!sep) { ++ eps = a2dp_select_eps(session, type, sender); ++ if (!eps) { + error("Unable to select SEP"); + return 0; + } +@@ -2599,8 +2620,9 @@ unsigned int a2dp_select_capabilities(struct avdtp *session, + cb_data->select_cb = cb; + cb_data->user_data = user_data; + +- setup->sep = sep; +- setup->rsep = find_remote_sep(setup->chan, sep); ++ setup->eps = eps; ++ setup->sep = queue_pop_head(eps); ++ setup->rsep = find_remote_sep(setup->chan, setup->sep); + + if (setup->rsep == NULL) { + error("Could not find remote sep"); +@@ -2610,10 +2632,11 @@ unsigned int a2dp_select_capabilities(struct avdtp *session, + service = avdtp_get_codec(setup->rsep->sep); + codec = (struct avdtp_media_codec_capability *) service->data; + +- err = sep->endpoint->select_configuration(sep, codec->data, ++ err = setup->sep->endpoint->select_configuration(setup->sep, ++ codec->data, + service->length - sizeof(*codec), + setup_ref(setup), +- select_cb, sep->user_data); ++ select_cb, setup->sep->user_data); + if (err == 0) + return cb_data->id; + +-- +2.21.0 + + +From 05e8894f97f09a2d17f3c9e8f398424218712ba8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Micha=C5=82=20Lowas-Rzechonek?= + +Date: Tue, 30 Apr 2019 12:30:00 +0200 +Subject: [PATCH 27/31] a2dp: Fixed warn_unused_result warning +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +This used to break builds when using maintainer mode via +./bootstrap-configure: + +profiles/audio/a2dp.c:1775:2: error: ignoring return value of + ‘asprintf’, declared with attribute warn_unused_result + [-Werror=unused-result] + asprintf(&sep->path, "%s/sep%d", + ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +--- + profiles/audio/a2dp.c | 11 +++++++++-- + 1 file changed, 9 insertions(+), 2 deletions(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index f10ba6c81..11afe0d05 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -1821,8 +1821,15 @@ static void register_remote_sep(void *data, void *user_data) + if (!(g_dbus_get_flags() & G_DBUS_FLAG_ENABLE_EXPERIMENTAL)) + goto done; + +- asprintf(&sep->path, "%s/sep%d", device_get_path(chan->device), +- avdtp_get_seid(rsep)); ++ if (asprintf(&sep->path, "%s/sep%d", ++ device_get_path(chan->device), ++ avdtp_get_seid(rsep)) < 0) { ++ error("Could not allocate path for remote sep %s/sep%d", ++ device_get_path(chan->device), ++ avdtp_get_seid(rsep)); ++ sep->path = NULL; ++ goto done; ++ } + + if (g_dbus_register_interface(btd_get_dbus_connection(), + sep->path, MEDIA_ENDPOINT_INTERFACE, +-- +2.21.0 + + +From 722eeec6864c1f84207431dfcdf4ef2321543d54 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 3 May 2019 10:51:34 +0300 +Subject: [PATCH 28/31] a2dp: Fix crash when endpoint respond with an error + +If endpoint respond with an error the callback will be called with size +set to -1 not 0. +--- + profiles/audio/a2dp.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 11afe0d05..24587b450 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -2418,7 +2418,7 @@ static void select_cb(struct a2dp_setup *setup, void *ret, int size) + struct avdtp_media_codec_capability *codec; + int err; + +- if (size) { ++ if (size >= 0) { + caps_add_codec(&setup->caps, setup->sep->codec, ret, size); + goto done; + } +-- +2.21.0 + + +From 0c97c4ecbedd0378c88899bc393f299fac048a48 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 3 May 2019 11:22:40 +0300 +Subject: [PATCH 29/31] a2dp: Try aborting when switching endpoints + +If ongoing stream cannot be closed try aborting since the setup may +still be ongoing. +--- + profiles/audio/a2dp.c | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 24587b450..36cc55da5 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -1257,6 +1257,11 @@ static void abort_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + if (!setup) + return; + ++ if (setup->reconfigure) { ++ g_timeout_add(RECONFIGURE_TIMEOUT, a2dp_reconfigure, setup); ++ return; ++ } ++ + setup_unref(setup); + } + +@@ -1642,8 +1647,12 @@ static int a2dp_reconfig(struct a2dp_channel *chan, const char *sender, + + err = avdtp_close(chan->session, tmp->stream, FALSE); + if (err < 0) { +- error("avdtp_close: %s", strerror(-err)); +- goto fail; ++ err = avdtp_abort(chan->session, tmp->stream); ++ if (err < 0) { ++ error("avdtp_abort: %s", ++ strerror(-err)); ++ goto fail; ++ } + } + + setup->reconfigure = TRUE; +-- +2.21.0 + + +From a71f4e02625f470f2bf5512caec6f157aef12d7e Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 3 May 2019 11:24:57 +0300 +Subject: [PATCH 30/31] a2dp: Update last used on open indication + +This updates the last used endpoint also when receiving an open +request from the remote end. +--- + profiles/audio/a2dp.c | 55 +++++++++++++++++++++++-------------------- + 1 file changed, 29 insertions(+), 26 deletions(-) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 36cc55da5..6d9c26b3d 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -836,32 +836,6 @@ static void getconf_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + DBG("Source %p: Set_Configuration_Cfm", sep); + } + +-static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep, +- struct avdtp_stream *stream, uint8_t *err, +- void *user_data) +-{ +- struct a2dp_sep *a2dp_sep = user_data; +- struct a2dp_setup *setup; +- +- if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) +- DBG("Sink %p: Open_Ind", sep); +- else +- DBG("Source %p: Open_Ind", sep); +- +- setup = a2dp_setup_get(session); +- if (!setup) +- return FALSE; +- +- setup->stream = stream; +- +- if (setup->reconfigure) +- setup->reconfigure = FALSE; +- +- finalize_config(setup); +- +- return TRUE; +-} +- + static bool match_remote_sep(const void *data, const void *user_data) + { + const struct a2dp_remote_sep *sep = data; +@@ -916,6 +890,35 @@ static void update_last_used(struct a2dp_channel *chan, + store_last_used(chan, rsep); + } + ++static gboolean open_ind(struct avdtp *session, struct avdtp_local_sep *sep, ++ struct avdtp_stream *stream, uint8_t *err, ++ void *user_data) ++{ ++ struct a2dp_sep *a2dp_sep = user_data; ++ struct a2dp_setup *setup; ++ ++ if (a2dp_sep->type == AVDTP_SEP_TYPE_SINK) ++ DBG("Sink %p: Open_Ind", sep); ++ else ++ DBG("Source %p: Open_Ind", sep); ++ ++ setup = a2dp_setup_get(session); ++ if (!setup) ++ return FALSE; ++ ++ setup->stream = stream; ++ ++ if (!err && setup->chan) ++ update_last_used(setup->chan, stream); ++ ++ if (setup->reconfigure) ++ setup->reconfigure = FALSE; ++ ++ finalize_config(setup); ++ ++ return TRUE; ++} ++ + static void open_cfm(struct avdtp *session, struct avdtp_local_sep *sep, + struct avdtp_stream *stream, struct avdtp_error *err, + void *user_data) +-- +2.21.0 + + +From fe6895ac63e37894ccaf17d4bec12e496d547c55 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 3 May 2019 12:41:15 +0300 +Subject: [PATCH 31/31] a2dp: Fix reconfiguring when there multiple devices + connected + +When there are multiple devices connected streams need to be matched +with the sessions they belong. +--- + profiles/audio/a2dp.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c +index 6d9c26b3d..b54c50315 100644 +--- a/profiles/audio/a2dp.c ++++ b/profiles/audio/a2dp.c +@@ -1648,6 +1648,10 @@ static int a2dp_reconfig(struct a2dp_channel *chan, const char *sender, + tmp->user_data))) + return -EPERM; + ++ /* Check if stream is for the channel */ ++ if (!avdtp_has_stream(chan->session, tmp->stream)) ++ continue; ++ + err = avdtp_close(chan->session, tmp->stream, FALSE); + if (err < 0) { + err = avdtp_abort(chan->session, tmp->stream); +-- +2.21.0 + diff --git a/bluez-5.50-autopair-backports.patch b/bluez-5.50-autopair-backports.patch new file mode 100644 index 0000000..8981042 --- /dev/null +++ b/bluez-5.50-autopair-backports.patch @@ -0,0 +1,81 @@ +From b83e617e98ddfce94f3209399c290c8a764ffe3e Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Mon, 27 Nov 2017 16:26:50 +0100 +Subject: [PATCH 1/2] autopair: Add more common PIN codes for audio devices + +PIN codes "1111", and "1234" are fairly common PIN codes used for audio +devices such as speakers and headsets. This replaces similar quirks +already present in gnome-bluetooth's PIN database. +--- + plugins/autopair.c | 18 ++++++++++++++---- + 1 file changed, 14 insertions(+), 4 deletions(-) + +diff --git a/plugins/autopair.c b/plugins/autopair.c +index 6980b0a64..70e19930e 100644 +--- a/plugins/autopair.c ++++ b/plugins/autopair.c +@@ -92,10 +92,20 @@ static ssize_t autopair_pincb(struct btd_adapter *adapter, + case 0x06: /* Headphones */ + case 0x07: /* Portable Audio */ + case 0x0a: /* HiFi Audio Device */ +- if (attempt > 1) +- return 0; +- memcpy(pinbuf, "0000", 4); +- return 4; ++ { ++ const char *pincodes[] = { ++ "0000", ++ "1234", ++ "1111" ++ }; ++ const char *pincode; ++ ++ if (attempt > G_N_ELEMENTS(pincodes)) ++ return 0; ++ pincode = pincodes[attempt - 1]; ++ memcpy(pinbuf, pincode, strlen(pincode)); ++ return strlen(pincode); ++ } + } + break; + +-- +2.21.0 + + +From 62d8a8237e3cb5bea5333d485416cc8d8b92556c Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Mon, 27 Nov 2017 16:26:51 +0100 +Subject: [PATCH 2/2] autopair: Add pin codes to try for gaming input devices + +As well as remote controls. +--- + plugins/autopair.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/plugins/autopair.c b/plugins/autopair.c +index 70e19930e..043bd9b9d 100644 +--- a/plugins/autopair.c ++++ b/plugins/autopair.c +@@ -111,6 +111,18 @@ static ssize_t autopair_pincb(struct btd_adapter *adapter, + + case 0x05: /* Peripheral */ + switch ((class & 0xc0) >> 6) { ++ case 0x00: ++ switch ((class & 0x1e) >> 2) { ++ case 0x01: /* Joystick */ ++ case 0x02: /* Gamepad */ ++ case 0x03: /* Remote Control */ ++ if (attempt > 1) ++ return 0; ++ memcpy(pinbuf, "0000", 4); ++ return 4; ++ } ++ ++ break; + case 0x01: /* Keyboard */ + case 0x03: /* Combo keyboard/pointing device */ + /* For keyboards rejecting the first random code +-- +2.21.0 + diff --git a/bluez-5.50-discoverability-backports.patch b/bluez-5.50-discoverability-backports.patch new file mode 100644 index 0000000..67d7d28 --- /dev/null +++ b/bluez-5.50-discoverability-backports.patch @@ -0,0 +1,1243 @@ +From c43718e84e290eaf8846baae0c1f6f1a4d464b78 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Tue, 24 Jul 2018 16:03:07 +0300 +Subject: [PATCH 01/15] client: Add discoverable-timeout command + +This adds discoverable-timeout command which can be used to get/set +DiscoverableTimeout property: + +[bluetooth]# discoverable-timeout 180 +Changing discoverable-timeout 180 succeeded +--- + client/main.c | 43 +++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 43 insertions(+) + +diff --git a/client/main.c b/client/main.c +index 87323d8f7..59820c6d9 100644 +--- a/client/main.c ++++ b/client/main.c +@@ -1061,6 +1061,47 @@ static void cmd_discoverable(int argc, char *argv[]) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } + ++static void cmd_discoverable_timeout(int argc, char *argv[]) ++{ ++ uint32_t value; ++ char *endptr = NULL; ++ char *str; ++ ++ if (argc < 2) { ++ DBusMessageIter iter; ++ ++ if (!g_dbus_proxy_get_property(default_ctrl->proxy, ++ "DiscoverableTimeout", &iter)) { ++ bt_shell_printf("Unable to get DiscoverableTimeout\n"); ++ return bt_shell_noninteractive_quit(EXIT_FAILURE); ++ } ++ ++ dbus_message_iter_get_basic(&iter, &value); ++ ++ bt_shell_printf("DiscoverableTimeout: %d seconds\n", value); ++ ++ return; ++ } ++ ++ value = strtol(argv[1], &endptr, 0); ++ if (!endptr || *endptr != '\0' || value > UINT32_MAX) { ++ bt_shell_printf("Invalid argument\n"); ++ return bt_shell_noninteractive_quit(EXIT_FAILURE); ++ } ++ ++ str = g_strdup_printf("discoverable-timeout %d", value); ++ ++ if (g_dbus_proxy_set_property_basic(default_ctrl->proxy, ++ "DiscoverableTimeout", ++ DBUS_TYPE_UINT32, &value, ++ generic_callback, str, g_free)) ++ return; ++ ++ g_free(str); ++ ++ return bt_shell_noninteractive_quit(EXIT_FAILURE); ++} ++ + static void cmd_agent(int argc, char *argv[]) + { + dbus_bool_t enable; +@@ -2549,6 +2590,8 @@ static const struct bt_shell_menu main_menu = { + { "discoverable", "", cmd_discoverable, + "Set controller discoverable mode", + NULL }, ++ { "discoverable-timeout", "[value]", cmd_discoverable_timeout, ++ "Set discoverable timeout", NULL }, + { "agent", "", cmd_agent, + "Enable/disable agent with given capability", + capability_generator}, +-- +2.21.0 + + +From 8d6f314f517b88fa286c888281a3d70797c94135 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Wed, 25 Jul 2018 10:22:45 +0300 +Subject: [PATCH 02/15] client: Make show command print DiscoverableTimeout + +Controller XX:XX:XX:XX:XX:XX (public) + Name: Vudentz's T460s + Alias: Intel-1 + Class: 0x004c010c + Powered: yes + Discoverable: no + DiscoverableTimeout: 0x00000000 + Pairable: yes + UUID: Headset AG (00001112-0000-1000-8000-00805f9b34fb) + UUID: Generic Attribute Profile (00001801-0000-1000-8000-00805f9b34fb) + UUID: A/V Remote Control (0000110e-0000-1000-8000-00805f9b34fb) + UUID: SIM Access (0000112d-0000-1000-8000-00805f9b34fb) + UUID: Generic Access Profile (00001800-0000-1000-8000-00805f9b34fb) + UUID: PnP Information (00001200-0000-1000-8000-00805f9b34fb) + UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb) + UUID: Audio Source (0000110a-0000-1000-8000-00805f9b34fb) + UUID: Audio Sink (0000110b-0000-1000-8000-00805f9b34fb) + UUID: Headset (00001108-0000-1000-8000-00805f9b34fb) + Modalias: usb:v1D6Bp0246d0532 + Discovering: no +--- + client/main.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/client/main.c b/client/main.c +index 59820c6d9..6f472d050 100644 +--- a/client/main.c ++++ b/client/main.c +@@ -877,6 +877,7 @@ static void cmd_show(int argc, char *argv[]) + print_property(proxy, "Class"); + print_property(proxy, "Powered"); + print_property(proxy, "Discoverable"); ++ print_property(proxy, "DiscoverableTimeout"); + print_property(proxy, "Pairable"); + print_uuids(proxy); + print_property(proxy, "Modalias"); +-- +2.21.0 + + +From 4dfe6d7691395dda5c711c290ec2087b3d4b79ee Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Wed, 25 Jul 2018 11:27:37 +0300 +Subject: [PATCH 03/15] adapter: Track pending settings + +This tracks settings being changed and in case the settings is already +pending considered it to be done. +--- + src/adapter.c | 30 ++++++++++++++++++++++++++++-- + 1 file changed, 28 insertions(+), 2 deletions(-) + +diff --git a/src/adapter.c b/src/adapter.c +index af340fd6e..20c20f9e9 100644 +--- a/src/adapter.c ++++ b/src/adapter.c +@@ -196,6 +196,7 @@ struct btd_adapter { + char *name; /* controller device name */ + char *short_name; /* controller short name */ + uint32_t supported_settings; /* controller supported settings */ ++ uint32_t pending_settings; /* pending controller settings */ + uint32_t current_settings; /* current controller settings */ + + char *path; /* adapter object path */ +@@ -509,8 +510,10 @@ static void settings_changed(struct btd_adapter *adapter, uint32_t settings) + changed_mask = adapter->current_settings ^ settings; + + adapter->current_settings = settings; ++ adapter->pending_settings &= ~changed_mask; + + DBG("Changed settings: 0x%08x", changed_mask); ++ DBG("Pending settings: 0x%08x", adapter->pending_settings); + + if (changed_mask & MGMT_SETTING_POWERED) { + g_dbus_emit_property_changed(dbus_conn, adapter->path, +@@ -596,10 +599,31 @@ static bool set_mode(struct btd_adapter *adapter, uint16_t opcode, + uint8_t mode) + { + struct mgmt_mode cp; ++ uint32_t setting = 0; + + memset(&cp, 0, sizeof(cp)); + cp.val = mode; + ++ switch (mode) { ++ case MGMT_OP_SET_POWERED: ++ setting = MGMT_SETTING_POWERED; ++ break; ++ case MGMT_OP_SET_CONNECTABLE: ++ setting = MGMT_SETTING_CONNECTABLE; ++ break; ++ case MGMT_OP_SET_FAST_CONNECTABLE: ++ setting = MGMT_SETTING_FAST_CONNECTABLE; ++ break; ++ case MGMT_OP_SET_DISCOVERABLE: ++ setting = MGMT_SETTING_DISCOVERABLE; ++ break; ++ case MGMT_OP_SET_BONDABLE: ++ setting = MGMT_SETTING_DISCOVERABLE; ++ break; ++ } ++ ++ adapter->pending_settings |= setting; ++ + DBG("sending set mode command for index %u", adapter->dev_id); + + if (mgmt_send(adapter->mgmt, opcode, +@@ -2739,13 +2763,15 @@ static void property_set_mode(struct btd_adapter *adapter, uint32_t setting, + else + current_enable = FALSE; + +- if (enable == current_enable) { ++ if (enable == current_enable || adapter->pending_settings & setting) { + g_dbus_pending_property_success(id); + return; + } + + mode = (enable == TRUE) ? 0x01 : 0x00; + ++ adapter->pending_settings |= setting; ++ + switch (setting) { + case MGMT_SETTING_POWERED: + opcode = MGMT_OP_SET_POWERED; +@@ -2798,7 +2824,7 @@ static void property_set_mode(struct btd_adapter *adapter, uint32_t setting, + data->id = id; + + if (mgmt_send(adapter->mgmt, opcode, adapter->dev_id, len, param, +- property_set_mode_complete, data, g_free) > 0) ++ property_set_mode_complete, data, g_free) > 0) + return; + + g_free(data); +-- +2.21.0 + + +From 307f81edd84cb104989154ec0d8f1c9ece3e048a Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Wed, 25 Jul 2018 11:39:55 +0300 +Subject: [PATCH 04/15] adapter: Check pending when setting DiscoverableTimeout + +This makes DiscoverableTimeout check if discoverable is already pending +and don't attempt to set it once again which may cause discoverable to +be re-enabled when in fact the application just want to set the timeout +alone. +--- + src/adapter.c | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +diff --git a/src/adapter.c b/src/adapter.c +index 20c20f9e9..f92c897c7 100644 +--- a/src/adapter.c ++++ b/src/adapter.c +@@ -2901,6 +2901,7 @@ static void property_set_discoverable_timeout( + GDBusPendingPropertySet id, void *user_data) + { + struct btd_adapter *adapter = user_data; ++ bool enabled; + dbus_uint32_t value; + + dbus_message_iter_get_basic(iter, &value); +@@ -2914,8 +2915,19 @@ static void property_set_discoverable_timeout( + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "DiscoverableTimeout"); + ++ if (adapter->pending_settings & MGMT_SETTING_DISCOVERABLE) { ++ if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) ++ enabled = false; ++ else ++ enabled = true; ++ } else { ++ if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) ++ enabled = true; ++ else ++ enabled = false; ++ } + +- if (adapter->current_settings & MGMT_SETTING_DISCOVERABLE) ++ if (enabled) + set_discoverable(adapter, 0x01, adapter->discoverable_timeout); + } + +-- +2.21.0 + + +From 17070fc01c9734fd4c63704fbf08f81aac49d1b6 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 27 Jul 2018 11:14:04 +0300 +Subject: [PATCH 05/15] core: Add AlwaysPairable to main.conf + +This adds a new option called AlwaysPairable to main.conf, it can be +used to enable Adapter.Pairable even in case there is no Agent +available. + +Since that could be consider a security problem to allow pairing +without user's consent the option defaults to false. +--- + src/adapter.c | 16 +++++++++++++++- + src/agent.h | 7 +++++++ + src/device.c | 2 -- + src/hcid.h | 1 + + src/main.c | 11 +++++++++++ + src/main.conf | 5 +++++ + 6 files changed, 39 insertions(+), 3 deletions(-) + +diff --git a/src/adapter.c b/src/adapter.c +index f92c897c7..f730c1639 100644 +--- a/src/adapter.c ++++ b/src/adapter.c +@@ -7792,6 +7792,19 @@ int adapter_set_io_capability(struct btd_adapter *adapter, uint8_t io_cap) + { + struct mgmt_cp_set_io_capability cp; + ++ if (!main_opts.pairable) { ++ if (io_cap == IO_CAPABILITY_INVALID) { ++ if (adapter->current_settings & MGMT_SETTING_BONDABLE) ++ set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x00); ++ ++ return 0; ++ } ++ ++ if (!(adapter->current_settings & MGMT_SETTING_BONDABLE)) ++ set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x01); ++ } else if (io_cap == IO_CAPABILITY_INVALID) ++ io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; ++ + memset(&cp, 0, sizeof(cp)); + cp.io_capability = io_cap; + +@@ -8720,7 +8733,8 @@ static void read_info_complete(uint8_t status, uint16_t length, + + set_name(adapter, btd_adapter_get_name(adapter)); + +- if (!(adapter->current_settings & MGMT_SETTING_BONDABLE)) ++ if (main_opts.pairable && ++ !(adapter->current_settings & MGMT_SETTING_BONDABLE)) + set_mode(adapter, MGMT_OP_SET_BONDABLE, 0x01); + + if (!kernel_conn_control) +diff --git a/src/agent.h b/src/agent.h +index 1e4692036..f14d14325 100644 +--- a/src/agent.h ++++ b/src/agent.h +@@ -22,6 +22,13 @@ + * + */ + ++#define IO_CAPABILITY_DISPLAYONLY 0x00 ++#define IO_CAPABILITY_DISPLAYYESNO 0x01 ++#define IO_CAPABILITY_KEYBOARDONLY 0x02 ++#define IO_CAPABILITY_NOINPUTNOOUTPUT 0x03 ++#define IO_CAPABILITY_KEYBOARDDISPLAY 0x04 ++#define IO_CAPABILITY_INVALID 0xFF ++ + struct agent; + + typedef void (*agent_cb) (struct agent *agent, DBusError *err, +diff --git a/src/device.c b/src/device.c +index 4f1af7012..0d7907a69 100644 +--- a/src/device.c ++++ b/src/device.c +@@ -75,8 +75,6 @@ + #include "attrib-server.h" + #include "eir.h" + +-#define IO_CAPABILITY_NOINPUTNOOUTPUT 0x03 +- + #define DISCONNECT_TIMER 2 + #define DISCOVERY_TIMER 1 + #define INVALID_FLAGS 0xff +diff --git a/src/hcid.h b/src/hcid.h +index 2c2b89d9c..ba250578a 100644 +--- a/src/hcid.h ++++ b/src/hcid.h +@@ -38,6 +38,7 @@ typedef enum { + struct main_opts { + char *name; + uint32_t class; ++ gboolean pairable; + uint32_t pairto; + uint32_t discovto; + uint8_t privacy; +diff --git a/src/main.c b/src/main.c +index 7e6af42cd..156406343 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -81,6 +81,7 @@ static const char *supported_options[] = { + "Name", + "Class", + "DiscoverableTimeout", ++ "AlwaysPairable" + "PairableTimeout", + "DeviceID", + "ReverseServiceDiscovery", +@@ -287,6 +288,16 @@ static void parse_config(GKeyFile *config) + main_opts.discovto = val; + } + ++ boolean = g_key_file_get_boolean(config, "General", ++ "AlwaysPairable", &err); ++ if (err) { ++ DBG("%s", err->message); ++ g_clear_error(&err); ++ } else { ++ DBG("pairable=%s", boolean ? "true" : "false"); ++ main_opts.pairable = boolean; ++ } ++ + val = g_key_file_get_integer(config, "General", + "PairableTimeout", &err); + if (err) { +diff --git a/src/main.conf b/src/main.conf +index cbae32ec5..0d480d183 100644 +--- a/src/main.conf ++++ b/src/main.conf +@@ -13,6 +13,11 @@ + # 0 = disable timer, i.e. stay discoverable forever + #DiscoverableTimeout = 0 + ++# Always allow pairing even if there are no agent registered ++# Possible values: true, false ++# Default: false ++#AlwaysPairable = false ++ + # How long to stay in pairable mode before going back to non-discoverable + # The value is in seconds. Default is 0. + # 0 = disable timer, i.e. stay pairable forever +-- +2.21.0 + + +From 27a5db2185864d7d4a8b51199c86b5c69ecc6fa3 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 27 Jul 2018 11:01:04 +0300 +Subject: [PATCH 06/15] agent: Make the first agent to register the default + +This simplifies the handling of default agent and enforce the IO +capabilities to be set whenever there is an agent available in the +system. +--- + src/agent.c | 14 ++++++-------- + 1 file changed, 6 insertions(+), 8 deletions(-) + +diff --git a/src/agent.c b/src/agent.c +index ff44d5755..183e2f190 100644 +--- a/src/agent.c ++++ b/src/agent.c +@@ -50,13 +50,6 @@ + #include "agent.h" + #include "shared/queue.h" + +-#define IO_CAPABILITY_DISPLAYONLY 0x00 +-#define IO_CAPABILITY_DISPLAYYESNO 0x01 +-#define IO_CAPABILITY_KEYBOARDONLY 0x02 +-#define IO_CAPABILITY_NOINPUTNOOUTPUT 0x03 +-#define IO_CAPABILITY_KEYBOARDDISPLAY 0x04 +-#define IO_CAPABILITY_INVALID 0xFF +- + #define REQUEST_TIMEOUT (60 * 1000) /* 60 seconds */ + #define AGENT_INTERFACE "org.bluez.Agent1" + +@@ -150,7 +143,7 @@ static void set_io_cap(struct btd_adapter *adapter, gpointer user_data) + if (agent) + io_cap = agent->capability; + else +- io_cap = IO_CAPABILITY_NOINPUTNOOUTPUT; ++ io_cap = IO_CAPABILITY_INVALID; + + adapter_set_io_capability(adapter, io_cap); + } +@@ -294,6 +287,11 @@ static struct agent *agent_create( const char *name, const char *path, + name, agent_disconnect, + agent, NULL); + ++ if (queue_isempty(default_agents)) ++ add_default_agent(agent); ++ else ++ queue_push_tail(default_agents, agent); ++ + return agent_ref(agent); + } + +-- +2.21.0 + + +From dc29a486f90e437de541f0e5644465a8f91121aa Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Thu, 26 Jul 2018 15:15:12 +0300 +Subject: [PATCH 07/15] doc/adapter-api: Add Discoverable option to + SetDiscoveryFilter + +This enables the client to set its discoverable setting while +discovering which is very typical situation as usually the setings +application would allow incoming pairing request while scanning, so +this would reduce the number of calls setting Discoverable and +DiscoverableTimeout and restoring after done with discovery. +--- + doc/adapter-api.txt | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/doc/adapter-api.txt b/doc/adapter-api.txt +index d14d0ca50..4791af2c7 100644 +--- a/doc/adapter-api.txt ++++ b/doc/adapter-api.txt +@@ -113,6 +113,12 @@ Methods void StartDiscovery() + generated for either ManufacturerData and + ServiceData everytime they are discovered. + ++ bool Discoverable (Default: false) ++ ++ Make adapter discoverable while discovering, ++ if the adapter is already discoverable this ++ setting this filter won't do anything. ++ + When discovery filter is set, Device objects will be + created as new devices with matching criteria are + discovered regardless of they are connectable or +-- +2.21.0 + + +From febbee4d7eee29a8e69039ec2aa82b7ea68163d1 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Thu, 26 Jul 2018 15:23:05 +0300 +Subject: [PATCH 08/15] adapter: Discovery filter discoverable + +This implements the discovery filter discoverable and tracks which +clients had enabled it and restores the settings when the last client +enabling it exits. +--- + src/adapter.c | 56 +++++++++++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 54 insertions(+), 2 deletions(-) + +diff --git a/src/adapter.c b/src/adapter.c +index f730c1639..ecd2c40ae 100644 +--- a/src/adapter.c ++++ b/src/adapter.c +@@ -157,6 +157,7 @@ struct discovery_filter { + int16_t rssi; + GSList *uuids; + bool duplicate; ++ bool discoverable; + }; + + struct watch_client { +@@ -219,6 +220,7 @@ struct btd_adapter { + uint8_t discovery_type; /* current active discovery type */ + uint8_t discovery_enable; /* discovery enabled/disabled */ + bool discovery_suspended; /* discovery has been suspended */ ++ bool discovery_discoverable; /* discoverable while discovering */ + GSList *discovery_list; /* list of discovery clients */ + GSList *set_filter_list; /* list of clients that specified + * filter, but don't scan yet +@@ -1842,6 +1844,20 @@ static void discovery_free(void *user_data) + g_free(client); + } + ++static bool set_discovery_discoverable(struct btd_adapter *adapter, bool enable) ++{ ++ if (adapter->discovery_discoverable == enable) ++ return true; ++ ++ /* Reset discoverable filter if already set */ ++ if (enable && (adapter->current_settings & MGMT_OP_SET_DISCOVERABLE)) ++ return true; ++ ++ adapter->discovery_discoverable = enable; ++ ++ return set_discoverable(adapter, enable, 0); ++} ++ + static void discovery_remove(struct watch_client *client) + { + struct btd_adapter *adapter = client->adapter; +@@ -2090,6 +2106,8 @@ static bool filters_equal(struct mgmt_cp_start_service_discovery *a, + static int update_discovery_filter(struct btd_adapter *adapter) + { + struct mgmt_cp_start_service_discovery *sd_cp; ++ GSList *l; ++ + + DBG(""); + +@@ -2099,6 +2117,18 @@ static int update_discovery_filter(struct btd_adapter *adapter) + return -ENOMEM; + } + ++ for (l = adapter->discovery_list; l; l = g_slist_next(l)) { ++ struct watch_client *client = l->data; ++ ++ if (!client->discovery_filter) ++ continue; ++ ++ if (client->discovery_filter->discoverable) ++ break; ++ } ++ ++ set_discovery_discoverable(adapter, l ? true : false); ++ + /* + * If filters are equal, then don't update scan, except for when + * starting discovery. +@@ -2130,6 +2160,9 @@ static int discovery_stop(struct watch_client *client) + return 0; + } + ++ if (adapter->discovery_discoverable) ++ set_discovery_discoverable(adapter, false); ++ + /* + * In the idle phase of a discovery, there is no need to stop it + * and so it is enough to send out the signal and just return. +@@ -2224,6 +2257,7 @@ static DBusMessage *start_discovery(DBusConnection *conn, + adapter->set_filter_list, client); + adapter->discovery_list = g_slist_prepend( + adapter->discovery_list, client); ++ + goto done; + } + +@@ -2348,6 +2382,17 @@ static bool parse_duplicate_data(DBusMessageIter *value, + return true; + } + ++static bool parse_discoverable(DBusMessageIter *value, ++ struct discovery_filter *filter) ++{ ++ if (dbus_message_iter_get_arg_type(value) != DBUS_TYPE_BOOLEAN) ++ return false; ++ ++ dbus_message_iter_get_basic(value, &filter->discoverable); ++ ++ return true; ++} ++ + struct filter_parser { + const char *name; + bool (*func)(DBusMessageIter *iter, struct discovery_filter *filter); +@@ -2357,6 +2402,7 @@ struct filter_parser { + { "Pathloss", parse_pathloss }, + { "Transport", parse_transport }, + { "DuplicateData", parse_duplicate_data }, ++ { "Discoverable", parse_discoverable }, + { } + }; + +@@ -2396,6 +2442,7 @@ static bool parse_discovery_filter_dict(struct btd_adapter *adapter, + (*filter)->rssi = DISTANCE_VAL_INVALID; + (*filter)->type = get_scan_type(adapter); + (*filter)->duplicate = false; ++ (*filter)->discoverable = false; + + dbus_message_iter_init(msg, &iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_ARRAY || +@@ -2441,8 +2488,10 @@ static bool parse_discovery_filter_dict(struct btd_adapter *adapter, + goto invalid_args; + + DBG("filtered discovery params: transport: %d rssi: %d pathloss: %d " +- " duplicate data: %s ", (*filter)->type, (*filter)->rssi, +- (*filter)->pathloss, (*filter)->duplicate ? "true" : "false"); ++ " duplicate data: %s discoverable %s", (*filter)->type, ++ (*filter)->rssi, (*filter)->pathloss, ++ (*filter)->duplicate ? "true" : "false", ++ (*filter)->discoverable ? "true" : "false"); + + return true; + +@@ -2880,6 +2929,9 @@ static void property_set_discoverable(const GDBusPropertyTable *property, + return; + } + ++ /* Reset discovery_discoverable as Discoverable takes precedence */ ++ adapter->discovery_discoverable = false; ++ + property_set_mode(adapter, MGMT_SETTING_DISCOVERABLE, iter, id); + } + +-- +2.21.0 + + +From c3ac3db663fe09c6c6b12a0738001e53d6971e29 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Thu, 26 Jul 2018 15:26:30 +0300 +Subject: [PATCH 09/15] client: Add scan.discoverable command + +This adds discoverable command to scan menu which can be used to set +if adapter should become discoverable while scanning: + +[bluetooth]# scan.discoverable on +[bluetooth]# scan on +SetDiscoveryFilter success +[CHG] Controller XX:XX:XX:XX:XX:XX Discoverable: yes +Discovery started +[CHG] Controller XX:XX:XX:XX:XX:XX Discovering: yes +[bluetooth]# scan off +Discovery stopped +[CHG] Controller XX:XX:XX:XX:XX:XX Discoverable: no +--- + client/main.c | 29 +++++++++++++++++++++++++++++ + 1 file changed, 29 insertions(+) + +diff --git a/client/main.c b/client/main.c +index 6f472d050..6e6f6d2fb 100644 +--- a/client/main.c ++++ b/client/main.c +@@ -1166,6 +1166,7 @@ static struct set_discovery_filter_args { + char **uuids; + size_t uuids_len; + dbus_bool_t duplicate; ++ dbus_bool_t discoverable; + bool set; + } filter = { + .rssi = DISTANCE_VAL_INVALID, +@@ -1205,6 +1206,11 @@ static void set_discovery_filter_setup(DBusMessageIter *iter, void *user_data) + DBUS_TYPE_BOOLEAN, + &args->duplicate); + ++ if (args->discoverable) ++ g_dbus_dict_append_entry(&dict, "Discoverable", ++ DBUS_TYPE_BOOLEAN, ++ &args->discoverable); ++ + dbus_message_iter_close_container(iter, &dict); + } + +@@ -1362,6 +1368,26 @@ static void cmd_scan_filter_duplicate_data(int argc, char *argv[]) + filter.set = false; + } + ++static void cmd_scan_filter_discoverable(int argc, char *argv[]) ++{ ++ if (argc < 2 || !strlen(argv[1])) { ++ bt_shell_printf("Discoverable: %s\n", ++ filter.discoverable ? "on" : "off"); ++ return bt_shell_noninteractive_quit(EXIT_SUCCESS); ++ } ++ ++ if (!strcmp(argv[1], "on")) ++ filter.discoverable = true; ++ else if (!strcmp(argv[1], "off")) ++ filter.discoverable = false; ++ else { ++ bt_shell_printf("Invalid option: %s\n", argv[1]); ++ return bt_shell_noninteractive_quit(EXIT_FAILURE); ++ } ++ ++ filter.set = false; ++} ++ + static void filter_clear_uuids(void) + { + g_strfreev(filter.uuids); +@@ -2510,6 +2536,9 @@ static const struct bt_shell_menu scan_menu = { + { "duplicate-data", "[on/off]", cmd_scan_filter_duplicate_data, + "Set/Get duplicate data filter", + NULL }, ++ { "discoverable", "[on/off]", cmd_scan_filter_discoverable, ++ "Set/Get discoverable filter", ++ NULL }, + { "clear", "[uuids/rssi/pathloss/transport/duplicate-data]", + cmd_scan_filter_clear, + "Clears discovery filter.", +-- +2.21.0 + + +From a8f14e570ab1b39c360ff194d0df54f7190704cf Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Thu, 26 Jul 2018 15:40:55 +0300 +Subject: [PATCH 10/15] client: Add scan.clear discoverable + +This implements scan.clear for discoverable filter. +--- + client/main.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/client/main.c b/client/main.c +index 6e6f6d2fb..1a66a3ab4 100644 +--- a/client/main.c ++++ b/client/main.c +@@ -1416,6 +1416,11 @@ static void filter_clear_duplicate(void) + filter.duplicate = false; + } + ++static void filter_clear_discoverable(void) ++{ ++ filter.discoverable = false; ++} ++ + struct clear_entry { + const char *name; + void (*clear) (void); +@@ -1427,6 +1432,7 @@ static const struct clear_entry filter_clear[] = { + { "pathloss", filter_clear_pathloss }, + { "transport", filter_clear_transport }, + { "duplicate-data", filter_clear_duplicate }, ++ { "discoverable", filter_clear_discoverable }, + {} + }; + +@@ -2539,7 +2545,8 @@ static const struct bt_shell_menu scan_menu = { + { "discoverable", "[on/off]", cmd_scan_filter_discoverable, + "Set/Get discoverable filter", + NULL }, +- { "clear", "[uuids/rssi/pathloss/transport/duplicate-data]", ++ { "clear", ++ "[uuids/rssi/pathloss/transport/duplicate-data/discoverable]", + cmd_scan_filter_clear, + "Clears discovery filter.", + filter_clear_generator }, +-- +2.21.0 + + +From 1ab3a106737f6b71dd6ebb5f180a25491212a5b7 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Thu, 26 Jul 2018 17:13:12 +0300 +Subject: [PATCH 11/15] adapter: Fix not keeping discovery filters + +If the discovery has been stopped and the client has set filters those +should be put back into filter list since the client may still be +interested in using them the next time it start a scanning. +--- + src/adapter.c | 25 ++++++++++++++++--------- + 1 file changed, 16 insertions(+), 9 deletions(-) + +diff --git a/src/adapter.c b/src/adapter.c +index ecd2c40ae..c24432125 100644 +--- a/src/adapter.c ++++ b/src/adapter.c +@@ -1858,7 +1858,7 @@ static bool set_discovery_discoverable(struct btd_adapter *adapter, bool enable) + return set_discoverable(adapter, enable, 0); + } + +-static void discovery_remove(struct watch_client *client) ++static void discovery_remove(struct watch_client *client, bool exit) + { + struct btd_adapter *adapter = client->adapter; + +@@ -1870,7 +1870,11 @@ static void discovery_remove(struct watch_client *client) + adapter->discovery_list = g_slist_remove(adapter->discovery_list, + client); + +- discovery_free(client); ++ if (!exit && client->discovery_filter) ++ adapter->set_filter_list = g_slist_prepend( ++ adapter->set_filter_list, client); ++ else ++ discovery_free(client); + + /* + * If there are other client discoveries in progress, then leave +@@ -1899,8 +1903,11 @@ static void stop_discovery_complete(uint8_t status, uint16_t length, + goto done; + } + +- if (client->msg) ++ if (client->msg) { + g_dbus_send_reply(dbus_conn, client->msg, DBUS_TYPE_INVALID); ++ dbus_message_unref(client->msg); ++ client->msg = NULL; ++ } + + adapter->discovery_type = 0x00; + adapter->discovery_enable = 0x00; +@@ -1913,7 +1920,7 @@ static void stop_discovery_complete(uint8_t status, uint16_t length, + trigger_passive_scanning(adapter); + + done: +- discovery_remove(client); ++ discovery_remove(client, false); + } + + static int compare_sender(gconstpointer a, gconstpointer b) +@@ -2148,14 +2155,14 @@ static int update_discovery_filter(struct btd_adapter *adapter) + return -EINPROGRESS; + } + +-static int discovery_stop(struct watch_client *client) ++static int discovery_stop(struct watch_client *client, bool exit) + { + struct btd_adapter *adapter = client->adapter; + struct mgmt_cp_stop_discovery cp; + + /* Check if there are more client discovering */ + if (g_slist_next(adapter->discovery_list)) { +- discovery_remove(client); ++ discovery_remove(client, exit); + update_discovery_filter(adapter); + return 0; + } +@@ -2168,7 +2175,7 @@ static int discovery_stop(struct watch_client *client) + * and so it is enough to send out the signal and just return. + */ + if (adapter->discovery_enable == 0x00) { +- discovery_remove(client); ++ discovery_remove(client, exit); + adapter->discovering = false; + g_dbus_emit_property_changed(dbus_conn, adapter->path, + ADAPTER_INTERFACE, "Discovering"); +@@ -2193,7 +2200,7 @@ static void discovery_disconnect(DBusConnection *conn, void *user_data) + + DBG("owner %s", client->owner); + +- discovery_stop(client); ++ discovery_stop(client, true); + } + + /* +@@ -2583,7 +2590,7 @@ static DBusMessage *stop_discovery(DBusConnection *conn, + if (client->msg) + return btd_error_busy(msg); + +- err = discovery_stop(client); ++ err = discovery_stop(client, false); + switch (err) { + case 0: + return dbus_message_new_method_return(msg); +-- +2.21.0 + + +From 38706123e9bf05169793589e7f452f3600ce4980 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Mon, 30 Jul 2018 14:11:38 +0300 +Subject: [PATCH 12/15] client: Commit changes to scan filter if active + +This detects if the command scan has been triggered and if so commit +changes to filter immediately so they take effect in the current +session. +--- + client/main.c | 55 +++++++++++++++++++++++++++++++++++---------------- + 1 file changed, 38 insertions(+), 17 deletions(-) + +diff --git a/client/main.c b/client/main.c +index 1a66a3ab4..4d848176c 100644 +--- a/client/main.c ++++ b/client/main.c +@@ -1139,6 +1139,24 @@ static void cmd_default_agent(int argc, char *argv[]) + agent_default(dbus_conn, agent_manager); + } + ++#define DISTANCE_VAL_INVALID 0x7FFF ++ ++static struct set_discovery_filter_args { ++ char *transport; ++ dbus_uint16_t rssi; ++ dbus_int16_t pathloss; ++ char **uuids; ++ size_t uuids_len; ++ dbus_bool_t duplicate; ++ dbus_bool_t discoverable; ++ bool set; ++ bool active; ++} filter = { ++ .rssi = DISTANCE_VAL_INVALID, ++ .pathloss = DISTANCE_VAL_INVALID, ++ .set = true, ++}; ++ + static void start_discovery_reply(DBusMessage *message, void *user_data) + { + dbus_bool_t enable = GPOINTER_TO_UINT(user_data); +@@ -1154,26 +1172,11 @@ static void start_discovery_reply(DBusMessage *message, void *user_data) + } + + bt_shell_printf("Discovery %s\n", enable ? "started" : "stopped"); ++ ++ filter.active = enable; + /* Leave the discovery running even on noninteractive mode */ + } + +-#define DISTANCE_VAL_INVALID 0x7FFF +- +-static struct set_discovery_filter_args { +- char *transport; +- dbus_uint16_t rssi; +- dbus_int16_t pathloss; +- char **uuids; +- size_t uuids_len; +- dbus_bool_t duplicate; +- dbus_bool_t discoverable; +- bool set; +-} filter = { +- .rssi = DISTANCE_VAL_INVALID, +- .pathloss = DISTANCE_VAL_INVALID, +- .set = true, +-}; +- + static void set_discovery_filter_setup(DBusMessageIter *iter, void *user_data) + { + struct set_discovery_filter_args *args = user_data; +@@ -1302,6 +1305,9 @@ static void cmd_scan_filter_uuids(int argc, char *argv[]) + + commit: + filter.set = false; ++ ++ if (filter.active) ++ set_discovery_filter(); + } + + static void cmd_scan_filter_rssi(int argc, char *argv[]) +@@ -1316,6 +1322,9 @@ static void cmd_scan_filter_rssi(int argc, char *argv[]) + filter.rssi = atoi(argv[1]); + + filter.set = false; ++ ++ if (filter.active) ++ set_discovery_filter(); + } + + static void cmd_scan_filter_pathloss(int argc, char *argv[]) +@@ -1331,6 +1340,9 @@ static void cmd_scan_filter_pathloss(int argc, char *argv[]) + filter.pathloss = atoi(argv[1]); + + filter.set = false; ++ ++ if (filter.active) ++ set_discovery_filter(); + } + + static void cmd_scan_filter_transport(int argc, char *argv[]) +@@ -1346,6 +1358,9 @@ static void cmd_scan_filter_transport(int argc, char *argv[]) + filter.transport = g_strdup(argv[1]); + + filter.set = false; ++ ++ if (filter.active) ++ set_discovery_filter(); + } + + static void cmd_scan_filter_duplicate_data(int argc, char *argv[]) +@@ -1366,6 +1381,9 @@ static void cmd_scan_filter_duplicate_data(int argc, char *argv[]) + } + + filter.set = false; ++ ++ if (filter.active) ++ set_discovery_filter(); + } + + static void cmd_scan_filter_discoverable(int argc, char *argv[]) +@@ -1386,6 +1404,9 @@ static void cmd_scan_filter_discoverable(int argc, char *argv[]) + } + + filter.set = false; ++ ++ if (filter.active) ++ set_discovery_filter(); + } + + static void filter_clear_uuids(void) +-- +2.21.0 + + +From 43e27149272b09159a9d15af74099fe901b24d66 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Wed, 1 Aug 2018 14:21:55 +0300 +Subject: [PATCH 13/15] client: Fix not resetting filters on scan.clear + +If call to SetDiscoveryFilter comes with any value set the daemon will +not attempt to clear the filters, instead the client is suppose to send +an empty dict. +--- + client/main.c | 39 ++++++++++++++++++++++++++++----------- + 1 file changed, 28 insertions(+), 11 deletions(-) + +diff --git a/client/main.c b/client/main.c +index 4d848176c..196b31a4d 100644 +--- a/client/main.c ++++ b/client/main.c +@@ -1177,6 +1177,19 @@ static void start_discovery_reply(DBusMessage *message, void *user_data) + /* Leave the discovery running even on noninteractive mode */ + } + ++static void clear_discovery_filter(DBusMessageIter *iter, void *user_data) ++{ ++ DBusMessageIter dict; ++ ++ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, ++ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING ++ DBUS_TYPE_STRING_AS_STRING ++ DBUS_TYPE_VARIANT_AS_STRING ++ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); ++ ++ dbus_message_iter_close_container(iter, &dict); ++} ++ + static void set_discovery_filter_setup(DBusMessageIter *iter, void *user_data) + { + struct set_discovery_filter_args *args = user_data; +@@ -1236,14 +1249,18 @@ static void set_discovery_filter_reply(DBusMessage *message, void *user_data) + return bt_shell_noninteractive_quit(EXIT_SUCCESS); + } + +-static void set_discovery_filter(void) ++static void set_discovery_filter(bool cleared) + { ++ GDBusSetupFunction func; ++ + if (check_default_ctrl() == FALSE || filter.set) + return; + ++ func = cleared ? clear_discovery_filter : set_discovery_filter_setup; ++ + if (g_dbus_proxy_method_call(default_ctrl->proxy, "SetDiscoveryFilter", +- set_discovery_filter_setup, set_discovery_filter_reply, +- &filter, NULL) == FALSE) { ++ func, set_discovery_filter_reply, ++ &filter, NULL) == FALSE) { + bt_shell_printf("Failed to set discovery filter\n"); + return bt_shell_noninteractive_quit(EXIT_FAILURE); + } +@@ -1263,7 +1280,7 @@ static void cmd_scan(int argc, char *argv[]) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + + if (enable == TRUE) { +- set_discovery_filter(); ++ set_discovery_filter(false); + method = "StartDiscovery"; + } else + method = "StopDiscovery"; +@@ -1307,7 +1324,7 @@ commit: + filter.set = false; + + if (filter.active) +- set_discovery_filter(); ++ set_discovery_filter(false); + } + + static void cmd_scan_filter_rssi(int argc, char *argv[]) +@@ -1324,7 +1341,7 @@ static void cmd_scan_filter_rssi(int argc, char *argv[]) + filter.set = false; + + if (filter.active) +- set_discovery_filter(); ++ set_discovery_filter(false); + } + + static void cmd_scan_filter_pathloss(int argc, char *argv[]) +@@ -1342,7 +1359,7 @@ static void cmd_scan_filter_pathloss(int argc, char *argv[]) + filter.set = false; + + if (filter.active) +- set_discovery_filter(); ++ set_discovery_filter(false); + } + + static void cmd_scan_filter_transport(int argc, char *argv[]) +@@ -1360,7 +1377,7 @@ static void cmd_scan_filter_transport(int argc, char *argv[]) + filter.set = false; + + if (filter.active) +- set_discovery_filter(); ++ set_discovery_filter(false); + } + + static void cmd_scan_filter_duplicate_data(int argc, char *argv[]) +@@ -1383,7 +1400,7 @@ static void cmd_scan_filter_duplicate_data(int argc, char *argv[]) + filter.set = false; + + if (filter.active) +- set_discovery_filter(); ++ set_discovery_filter(false); + } + + static void cmd_scan_filter_discoverable(int argc, char *argv[]) +@@ -1406,7 +1423,7 @@ static void cmd_scan_filter_discoverable(int argc, char *argv[]) + filter.set = false; + + if (filter.active) +- set_discovery_filter(); ++ set_discovery_filter(false); + } + + static void filter_clear_uuids(void) +@@ -1518,7 +1535,7 @@ static void cmd_scan_filter_clear(int argc, char *argv[]) + if (check_default_ctrl() == FALSE) + return bt_shell_noninteractive_quit(EXIT_FAILURE); + +- set_discovery_filter(); ++ set_discovery_filter(all); + } + + static struct GDBusProxy *find_device(int argc, char *argv[]) +-- +2.21.0 + + +From b47955a4003910c9eacac3d6e96bfb4f545e33e0 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Wed, 1 Aug 2018 16:08:36 +0300 +Subject: [PATCH 14/15] doc/adapter-api: Fix working of Discoverable filter + +--- + doc/adapter-api.txt | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/doc/adapter-api.txt b/doc/adapter-api.txt +index 4791af2c7..2afd61bc3 100644 +--- a/doc/adapter-api.txt ++++ b/doc/adapter-api.txt +@@ -116,8 +116,8 @@ Methods void StartDiscovery() + bool Discoverable (Default: false) + + Make adapter discoverable while discovering, +- if the adapter is already discoverable this +- setting this filter won't do anything. ++ if the adapter is already discoverable setting ++ this filter won't do anything. + + When discovery filter is set, Device objects will be + created as new devices with matching criteria are +-- +2.21.0 + + +From 608b35d6354d22092af7f6907ce7a974f9cfb3d2 Mon Sep 17 00:00:00 2001 +From: Luiz Augusto von Dentz +Date: Fri, 28 Sep 2018 16:08:32 +0300 +Subject: [PATCH 15/15] sdp: Fix buffer overflow +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +sdp_append_buf shall check if there is enough space to store the data +before copying it. + +An independent security researcher, Julian Rauchberger, has reported +this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure +program. +--- + lib/sdp.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/lib/sdp.c b/lib/sdp.c +index eb408a948..84311eda1 100644 +--- a/lib/sdp.c ++++ b/lib/sdp.c +@@ -2834,6 +2834,12 @@ void sdp_append_to_buf(sdp_buf_t *dst, uint8_t *data, uint32_t len) + SDPDBG("Append src size: %d", len); + SDPDBG("Append dst size: %d", dst->data_size); + SDPDBG("Dst buffer size: %d", dst->buf_size); ++ ++ if (dst->data_size + len > dst->buf_size) { ++ SDPERR("Cannot append"); ++ return; ++ } ++ + if (dst->data_size == 0 && dtd == 0) { + /* create initial sequence */ + *p = SDP_SEQ8; +-- +2.21.0 + diff --git a/bluez-5.50-sixaxis-fixes.patch b/bluez-5.50-sixaxis-fixes.patch new file mode 100644 index 0000000..403100d --- /dev/null +++ b/bluez-5.50-sixaxis-fixes.patch @@ -0,0 +1,60 @@ +From f12e00b5127589589d476817cb528d442c30b2c7 Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 6 Jun 2019 18:42:42 +0200 +Subject: [PATCH 1/2] sixaxis: Fix another problem with already setup devices + +If the device went through any kind of pairing once, it might have been +set as trusted. Make sure to set the device as untrusted before starting +the cable pairing authorization so that we don't exit early from +process_auth_queue() (which considers trusted devices to be paired). +--- + plugins/sixaxis.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/plugins/sixaxis.c b/plugins/sixaxis.c +index fed68d2d8..12638829b 100644 +--- a/plugins/sixaxis.c ++++ b/plugins/sixaxis.c +@@ -365,6 +365,7 @@ static bool setup_device(int fd, const char *sysfs_path, + + btd_device_device_set_name(device, cp->name); + btd_device_set_pnpid(device, cp->source, cp->vid, cp->pid, cp->version); ++ btd_device_set_trusted(device, false); + btd_device_set_temporary(device, true); + + closure = g_new0(struct authentication_closure, 1); +-- +2.21.0 + + +From 32f6e03823970796b7e08718fe28c76c96de32ac Mon Sep 17 00:00:00 2001 +From: Bastien Nocera +Date: Thu, 6 Jun 2019 18:46:23 +0200 +Subject: [PATCH 2/2] sixaxis: Throw an error when cable setup fails + +If btd_request_authorization_cable_configured() fails, throw an error +and free resources. +--- + plugins/sixaxis.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/plugins/sixaxis.c b/plugins/sixaxis.c +index 12638829b..939fed759 100644 +--- a/plugins/sixaxis.c ++++ b/plugins/sixaxis.c +@@ -384,6 +384,12 @@ static bool setup_device(int fd, const char *sysfs_path, + adapter_bdaddr, &device_bdaddr, + HID_UUID, agent_auth_cb, closure); + ++ if (closure->auth_id == 0) { ++ error("sixaxis: could not request cable authorization"); ++ auth_closure_destroy(closure, true); ++ return false; ++ } ++ + g_hash_table_insert(pending_auths, closure->sysfs_path, closure); + + return true; +-- +2.21.0 + diff --git a/bluez.spec b/bluez.spec index ee11c34..244a1f1 100644 --- a/bluez.spec +++ b/bluez.spec @@ -1,7 +1,7 @@ Name: bluez Summary: Bluetooth utilities Version: 5.50 -Release: 7%{?dist} +Release: 8%{?dist} License: GPLv2+ URL: http://www.bluez.org/ @@ -30,6 +30,18 @@ Patch23: 0004-systemd-More-lockdown.patch # Fix A2DP disconnection with some headsets Patch30: 0001-policy-Add-logic-to-connect-a-Sink.patch +# Backports: +# https://bugzilla.redhat.com/show_bug.cgi?id=1711594 +Patch40: 0001-bluetooth.conf-remove-deprecated-at_console-statemen.patch +# autopair enhancements +Patch41: bluez-5.50-autopair-backports.patch +# Discoverable filters support, as used in gnome-bluetooth +Patch42: bluez-5.50-discoverability-backports.patch +# a2dp fixes for newer codecs +Patch43: bluez-5.50-a2dp-backports.patch +# sixaxis pairing fixes +Patch44: bluez-5.50-sixaxis-fixes.patch + BuildRequires: git-core BuildRequires: dbus-devel >= 1.6 BuildRequires: glib2-devel @@ -271,6 +283,14 @@ install -D -p -m0755 %{SOURCE4} ${RPM_BUILD_ROOT}/%{_libexecdir}/bluetooth/ %{_userunitdir}/obex.service %changelog +* Thu Jun 06 2019 Bastien Nocera - 5.50-8 ++ bluez-5.50-8 +- Backport loads of fixes from upstream, including: + - dbus-broker support (#1711594) + - a2dp codecs discovery + - discoverability filter support (used in gnome-bluetooth, #1583442) + - sixaxis pairing fixes + * Tue Apr 16 2019 Eduardo Minguez - 5.50-7 - Added avinfo