audio/avrcp: Guard SetAbsoluteVolume without target behind config value
authorMarijn Suijten <marijn.suijten@somainline.org>
Sat, 5 Oct 2024 21:43:06 +0000 (23:43 +0200)
committerWootak Jung <wootak.jung@samsung.com>
Thu, 20 Feb 2025 07:43:23 +0000 (16:43 +0900)
Commit 179ccb936 ("avrcp: Set volume if volume changed event is
registered") invented a workaround that allows SetAbsoluteVolume to be
sent to a remote device that does _not_ implement the AVRCP TG profile,
as long as it previously registered for the EVENT_VOLUME_CHANGED
notification.  This is strange as the TG role is required to be able to
send commands to the peer, but the commit must have been applied to the
tree for a reason.

We discussed in [1] that workarounds for dubious peers and software
stacks should be guarded behind a config entry in main.conf, so this
starts out by introducing a new [AVRCP] category that will later be
extended with other workarounds.  It guards the changed functionality
behind a `VolumeWithoutTarget = false` boolean to disallow this obscure
check.

[1]: https://lore.kernel.org/linux-bluetooth/20211025210206.bkt5wovzmkmt6teg@SoMainline.org/

Signed-off-by: Anuj Jain <anuj01.jain@samsung.com>
profiles/audio/avrcp.c
src/btd.h
src/main.c
src/main.conf

index bb675046966d3ebc845a79fc9a553a6fbff6ead5..d17608646cde3bac0d0b9b0ee55090b3c6339d4e 100644 (file)
@@ -47,6 +47,7 @@
 #include "src/dbus-common.h"
 #include "src/shared/timeout.h"
 #include "src/shared/util.h"
+#include "src/btd.h"
 
 #include "avctp.h"
 #include "avrcp.h"
@@ -5209,9 +5210,18 @@ int avrcp_set_volume(struct btd_device *dev, int8_t volume, bool notify)
                                                                &volume);
        }
 
-       if (!session->controller && !avrcp_event_registered(session,
-                                       AVRCP_EVENT_VOLUME_CHANGED))
+       if (btd_opts.avrcp.volume_without_target) {
+               /* If there is no target profile (we didn't create a controller
+                * for it), allow the call to pass through if the remote
+                * controller registered for a volume changed event.
+                */
+               if (!session->controller && !avrcp_event_registered(session,
+                                               AVRCP_EVENT_VOLUME_CHANGED))
+                       return -ENOTSUP;
+       } else if (!session->controller ||
+                               session->controller->version < 0x0104) {
                return -ENOTSUP;
+       }
 
        memset(buf, 0, sizeof(buf));
 
index dcc53fb7472eff94cec5ceb733b244dac535ca3c..e4730bde4e5388990bb60773fe80ca5c93406568 100755 (executable)
--- a/src/btd.h
+++ b/src/btd.h
@@ -104,6 +104,10 @@ struct btd_avdtp_opts {
        uint8_t  stream_mode;
 };
 
+struct btd_avrcp_opts {
+       bool            volume_without_target;
+};
+
 struct btd_advmon_opts {
        uint8_t         rssi_sampling_period;
 };
@@ -152,6 +156,7 @@ struct btd_opts {
        enum mps_mode_t mps;
 
        struct btd_avdtp_opts avdtp;
+       struct btd_avrcp_opts avrcp;
 
        uint8_t         key_size;
 
index 8402e7f007885b2c5dbb2876739afda1f9489675..b64ae858a27d17a6d2ba054c2dc75b866fec90f7 100755 (executable)
@@ -172,6 +172,11 @@ static const char *avdtp_options[] = {
        NULL
 };
 
+static const char *avrcp_options[] = {
+       "VolumeWithoutTarget",
+       NULL
+};
+
 static const char *advmon_options[] = {
        "RSSISamplingPeriod",
        NULL
@@ -188,6 +193,7 @@ static const struct group_table {
        { "GATT",       gatt_options },
        { "CSIS",       csip_options },
        { "AVDTP",      avdtp_options },
+       { "AVRCP",      avrcp_options },
        { "AdvMon",     advmon_options },
        { }
 };
@@ -1189,6 +1195,13 @@ static void parse_avdtp(GKeyFile *config)
        parse_avdtp_stream_mode(config);
 }
 
+static void parse_avrcp(GKeyFile *config)
+{
+       parse_config_bool(config, "AVRCP",
+               "VolumeWithoutTarget",
+               &btd_opts.avrcp.volume_without_target);
+}
+
 static void parse_advmon(GKeyFile *config)
 {
        parse_config_u8(config, "AdvMon", "RSSISamplingPeriod",
@@ -1212,6 +1225,7 @@ static void parse_config(GKeyFile *config)
        parse_gatt(config);
        parse_csis(config);
        parse_avdtp(config);
+       parse_avrcp(config);
        parse_advmon(config);
 }
 
@@ -1259,6 +1273,8 @@ static void init_defaults(void)
        btd_opts.avdtp.session_mode = BT_IO_MODE_BASIC;
        btd_opts.avdtp.stream_mode = BT_IO_MODE_BASIC;
 
+       btd_opts.avrcp.volume_without_target = false;
+
        btd_opts.advmon.rssi_sampling_period = 0xFF;
        btd_opts.csis.encrypt = true;
 #endif
index d2ca80fd76d460a51ebff1f03832e1209c78a0ca..2100d4a46e981314730211d5678e40e29573b22f 100755 (executable)
 # streaming: Use L2CAP Streaming Mode
 #StreamMode = basic
 
+[AVRCP]
+# Allow SetAbsoluteVolume calls to a peer device that does not advertise the
+# AVRCP remote control target profile.  If it does advertise this profile, the
+# version is ignored.
+#VolumeWithoutTarget = false
+
 [Policy]
 #
 # The ReconnectUUIDs defines the set of remote services that should try