Linux bluetooth development
 help / color / mirror / Atom feed
From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v1 4/5] avrcp: Move response parsers to avrcp-parse.c
Date: Tue,  1 Sep 2026 13:53:14 -0400	[thread overview]
Message-ID: <20260901175315.1348621-4-luiz.dentz@gmail.com> (raw)
In-Reply-To: <20260901175315.1348621-1-luiz.dentz@gmail.com>

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

The parsing of controller side responses lives in the middle of
avrcp.c, interleaved with the media_player and D-Bus glue that consumes
its results. That makes it unreachable from the unit tests: none of it
can be called without an adapter, a device, a service and a session.

The three preceding fixes were all in this code, and none of them could
be covered by a regression test as a result.

Move the parsing proper to a new avrcp-parse.c, which depends on
nothing but util_iov and log.h:

- avrcp_pull_header() and avrcp_pull_browsing_header()
- avrcp_parse_player_attributes(), which now takes the bound on the
  attribute array from its caller
- avrcp_parse_attribute_list(), which reports each attribute through a
  callback rather than calling media_player_set_metadata() itself
- avrcp_parse_media_name(), avrcp_parse_media_element() and
  avrcp_parse_media_folder(), which fill a plain struct rather than
  creating media items

struct avrcp_header, struct avrcp_browsing_header, NAME_MAX_LEN and the
player attribute ids move to the new header, which avrcp.h now includes
so that its users are unaffected.

The logic is unchanged; avrcp.c keeps the glue as thin wrappers.

Assisted-by: Claude:claude-opus-5 valgrind
---
 Makefile.plugins             |   2 +
 profiles/audio/avrcp-parse.c | 174 +++++++++++++++++++++++++++
 profiles/audio/avrcp-parse.h | 102 ++++++++++++++++
 profiles/audio/avrcp.c       | 221 +++++++----------------------------
 profiles/audio/avrcp.h       |   8 +-
 5 files changed, 322 insertions(+), 185 deletions(-)
 create mode 100644 profiles/audio/avrcp-parse.c
 create mode 100644 profiles/audio/avrcp-parse.h

diff --git a/Makefile.plugins b/Makefile.plugins
index ac667beda847..a73569e555fe 100644
--- a/Makefile.plugins
+++ b/Makefile.plugins
@@ -37,6 +37,8 @@ builtin_modules += avrcp
 builtin_sources += profiles/audio/control.h profiles/audio/control.c \
 			profiles/audio/avctp.h profiles/audio/avctp.c \
 			profiles/audio/avrcp.h profiles/audio/avrcp.c \
+			profiles/audio/avrcp-parse.h \
+			profiles/audio/avrcp-parse.c \
 			profiles/audio/avrcp-player.c
 endif
 
diff --git a/profiles/audio/avrcp-parse.c b/profiles/audio/avrcp-parse.c
new file mode 100644
index 000000000000..e0c73ec9b03a
--- /dev/null
+++ b/profiles/audio/avrcp-parse.c
@@ -0,0 +1,174 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Intel Corporation
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/uio.h>
+
+#include "src/log.h"
+#include "src/shared/util.h"
+
+#include "avrcp-parse.h"
+
+/*
+ * Pull the AVRCP header out of iov and validate that the parameters length
+ * it declares matches the number of bytes actually received, leaving iov
+ * pointing at the parameters.
+ */
+struct avrcp_header *avrcp_pull_header(struct iovec *iov)
+{
+	struct avrcp_header *pdu;
+
+	pdu = util_iov_pull_mem(iov, sizeof(*pdu));
+	if (!pdu) {
+		error("Invalid AVRCP header");
+		return NULL;
+	}
+
+	if (be16_to_cpu(pdu->params_len) != iov->iov_len) {
+		error("Invalid parameters");
+		return NULL;
+	}
+
+	return pdu;
+}
+
+/*
+ * Same as avrcp_pull_header() but for the browsing channel, which uses a
+ * different header layout.
+ */
+struct avrcp_browsing_header *avrcp_pull_browsing_header(struct iovec *iov)
+{
+	struct avrcp_browsing_header *pdu;
+
+	pdu = util_iov_pull_mem(iov, sizeof(*pdu));
+	if (!pdu) {
+		error("Invalid AVRCP browsing header");
+		return NULL;
+	}
+
+	if (be16_to_cpu(pdu->param_len) != iov->iov_len) {
+		error("Invalid parameters");
+		return NULL;
+	}
+
+	return pdu;
+}
+
+/*
+ * Pull a ListPlayerApplicationSettingAttributes response body out of iov,
+ * skipping the attributes that cannot be queried. At most max attributes are
+ * written to attrs, which is what bounds the write.
+ */
+uint8_t avrcp_parse_player_attributes(struct iovec *iov, uint8_t *attrs,
+							uint8_t max)
+{
+	uint8_t len, count = 0;
+	int i;
+
+	if (!util_iov_pull_u8(iov, &len))
+		return 0;
+
+	len = MIN(len, max);
+
+	for (i = 0; i < len; i++) {
+		uint8_t attr;
+
+		if (!util_iov_pull_u8(iov, &attr))
+			break;
+
+		/* Don't query invalid attributes */
+		if (attr == AVRCP_ATTRIBUTE_ILLEGAL ||
+					attr > AVRCP_ATTRIBUTE_LAST)
+			continue;
+
+		attrs[count++] = attr;
+	}
+
+	return count;
+}
+
+void avrcp_parse_attribute_list(struct iovec *iov, uint8_t count,
+					avrcp_attribute_func_t func,
+					void *user_data)
+{
+	for (; count > 0; count--) {
+		struct avrcp_attribute attr;
+
+		if (!util_iov_pull_be32(iov, &attr.id) ||
+				!util_iov_pull_be16(iov, &attr.charset) ||
+				!util_iov_pull_be16(iov, &attr.len))
+			return;
+
+		attr.value = util_iov_pull_mem(iov, attr.len);
+		if (!attr.value)
+			return;
+
+		func(&attr, user_data);
+	}
+}
+
+bool avrcp_parse_media_name(struct iovec *iov, char *name)
+{
+	uint16_t namelen;
+	uint8_t *namebuf;
+
+	if (!util_iov_pull_be16(iov, &namelen))
+		return false;
+
+	namebuf = util_iov_pull_mem(iov, namelen);
+	if (!namebuf)
+		return false;
+
+	namelen = MIN(namelen, NAME_MAX_LEN - 1);
+
+	memset(name, 0, NAME_MAX_LEN);
+	memcpy(name, namebuf, namelen);
+	strtoutf8(name, namelen);
+
+	return true;
+}
+
+bool avrcp_parse_media_element(struct iovec *iov,
+					struct avrcp_media_element *element)
+{
+	/* Skip the media type and character set */
+	if (!util_iov_pull_be64(iov, &element->uid) || !util_iov_pull(iov, 3))
+		return false;
+
+	if (!avrcp_parse_media_name(iov, element->name))
+		return false;
+
+	if (!util_iov_pull_u8(iov, &element->count))
+		return false;
+
+	return true;
+}
+
+bool avrcp_parse_media_folder(struct iovec *iov,
+					struct avrcp_media_folder *folder)
+{
+	/* Skip the character set */
+	if (!util_iov_pull_be64(iov, &folder->uid) ||
+			!util_iov_pull_u8(iov, &folder->type) ||
+			!util_iov_pull_u8(iov, &folder->playable) ||
+			!util_iov_pull(iov, 2))
+		return false;
+
+	if (!avrcp_parse_media_name(iov, folder->name))
+		return false;
+
+	return true;
+}
diff --git a/profiles/audio/avrcp-parse.h b/profiles/audio/avrcp-parse.h
new file mode 100644
index 000000000000..1a27c0684b32
--- /dev/null
+++ b/profiles/audio/avrcp-parse.h
@@ -0,0 +1,102 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Intel Corporation
+ *
+ *
+ */
+
+#ifndef __AVRCP_PARSE_H
+#define __AVRCP_PARSE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/uio.h>
+
+#define NAME_MAX_LEN 255
+
+/* player attributes */
+#define AVRCP_ATTRIBUTE_ILLEGAL		0x00
+#define AVRCP_ATTRIBUTE_EQUALIZER	0x01
+#define AVRCP_ATTRIBUTE_REPEAT_MODE	0x02
+#define AVRCP_ATTRIBUTE_SHUFFLE		0x03
+#define AVRCP_ATTRIBUTE_SCAN		0x04
+#define AVRCP_ATTRIBUTE_LAST		AVRCP_ATTRIBUTE_SCAN
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avrcp_header {
+	uint8_t company_id[3];
+	uint8_t pdu_id;
+	uint8_t packet_type:2;
+	uint8_t rsvd:6;
+	uint16_t params_len;
+	uint8_t params[0];
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avrcp_header {
+	uint8_t company_id[3];
+	uint8_t pdu_id;
+	uint8_t rsvd:6;
+	uint8_t packet_type:2;
+	uint16_t params_len;
+	uint8_t params[0];
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+#define AVRCP_HEADER_LENGTH 7
+
+struct avrcp_browsing_header {
+	uint8_t pdu_id;
+	uint16_t param_len;
+	uint8_t params[0];
+} __attribute__ ((packed));
+#define AVRCP_BROWSING_HEADER_LENGTH 3
+
+struct avrcp_attribute {
+	uint32_t id;
+	uint16_t charset;
+	uint16_t len;
+	uint8_t *value;
+};
+
+struct avrcp_media_element {
+	uint64_t uid;
+	char name[NAME_MAX_LEN];
+	uint8_t count;
+};
+
+struct avrcp_media_folder {
+	uint64_t uid;
+	uint8_t type;
+	uint8_t playable;
+	char name[NAME_MAX_LEN];
+};
+
+typedef void (*avrcp_attribute_func_t)(const struct avrcp_attribute *attr,
+							void *user_data);
+
+struct avrcp_header *avrcp_pull_header(struct iovec *iov);
+struct avrcp_browsing_header *avrcp_pull_browsing_header(struct iovec *iov);
+
+uint8_t avrcp_parse_player_attributes(struct iovec *iov, uint8_t *attrs,
+							uint8_t max);
+
+void avrcp_parse_attribute_list(struct iovec *iov, uint8_t count,
+					avrcp_attribute_func_t func,
+					void *user_data);
+
+bool avrcp_parse_media_name(struct iovec *iov, char *name);
+bool avrcp_parse_media_element(struct iovec *iov,
+					struct avrcp_media_element *element);
+bool avrcp_parse_media_folder(struct iovec *iov,
+					struct avrcp_media_folder *folder);
+
+#endif /* __AVRCP_PARSE_H */
diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c
index aff3b5ce21a2..df5f97eafc88 100644
--- a/profiles/audio/avrcp.c
+++ b/profiles/audio/avrcp.c
@@ -52,6 +52,7 @@
 
 #include "avctp.h"
 #include "avrcp.h"
+#include "avrcp-parse.h"
 #include "control.h"
 #include "media.h"
 #include "player.h"
@@ -145,46 +146,9 @@
 #define AVRCP_SCOPE_SEARCH				0x02
 #define AVRCP_SCOPE_NOW_PLAYING			0x03
 
-#define NAME_MAX_LEN 255
-
-#if __BYTE_ORDER == __LITTLE_ENDIAN
-
-struct avrcp_header {
-	uint8_t company_id[3];
-	uint8_t pdu_id;
-	uint8_t packet_type:2;
-	uint8_t rsvd:6;
-	uint16_t params_len;
-	uint8_t params[0];
-} __attribute__ ((packed));
-#define AVRCP_HEADER_LENGTH 7
-
-#elif __BYTE_ORDER == __BIG_ENDIAN
-
-struct avrcp_header {
-	uint8_t company_id[3];
-	uint8_t pdu_id;
-	uint8_t rsvd:6;
-	uint8_t packet_type:2;
-	uint16_t params_len;
-	uint8_t params[0];
-} __attribute__ ((packed));
-#define AVRCP_HEADER_LENGTH 7
-
-#else
-#error "Unknown byte order"
-#endif
-
 #define AVRCP_MTU	(AVC_MTU - AVC_HEADER_LENGTH)
 #define AVRCP_PDU_MTU	(AVRCP_MTU - AVRCP_HEADER_LENGTH)
 
-struct avrcp_browsing_header {
-	uint8_t pdu_id;
-	uint16_t param_len;
-	uint8_t params[0];
-} __attribute__ ((packed));
-#define AVRCP_BROWSING_HEADER_LENGTH 3
-
 struct get_folder_items_rsp {
 	uint8_t status;
 	uint16_t uid_counter;
@@ -2260,52 +2224,6 @@ static const char *status_to_string(uint8_t status)
 	}
 }
 
-/*
- * Pull the AVRCP header out of iov and validate that the parameters length
- * it declares matches the number of bytes actually received, leaving iov
- * pointing at the parameters.
- */
-static struct avrcp_header *avrcp_pull_header(struct iovec *iov)
-{
-	struct avrcp_header *pdu;
-
-	pdu = util_iov_pull_mem(iov, sizeof(*pdu));
-	if (!pdu) {
-		error("Invalid AVRCP header");
-		return NULL;
-	}
-
-	if (be16_to_cpu(pdu->params_len) != iov->iov_len) {
-		error("Invalid parameters");
-		return NULL;
-	}
-
-	return pdu;
-}
-
-/*
- * Same as avrcp_pull_header() but for the browsing channel, which uses a
- * different header layout.
- */
-static struct avrcp_browsing_header *avrcp_pull_browsing_header(
-							struct iovec *iov)
-{
-	struct avrcp_browsing_header *pdu;
-
-	pdu = util_iov_pull_mem(iov, sizeof(*pdu));
-	if (!pdu) {
-		error("Invalid AVRCP browsing header");
-		return NULL;
-	}
-
-	if (be16_to_cpu(pdu->param_len) != iov->iov_len) {
-		error("Invalid parameters");
-		return NULL;
-	}
-
-	return pdu;
-}
-
 static gboolean avrcp_get_play_status_rsp(struct avctp *conn, uint8_t code,
 					uint8_t subunit, uint8_t transaction,
 					uint8_t *operands, size_t operand_count,
@@ -2452,8 +2370,7 @@ static gboolean avrcp_list_player_attributes_rsp(struct avctp *conn,
 	struct iovec iov = { operands, operand_count };
 	uint8_t attrs[AVRCP_ATTRIBUTE_LAST];
 	struct avrcp *session = user_data;
-	uint8_t len, count = 0;
-	int i;
+	uint8_t count;
 
 	if (code == AVC_CTYPE_REJECTED || code == AVC_CTYPE_NOT_IMPLEMENTED)
 		return FALSE;
@@ -2461,25 +2378,7 @@ static gboolean avrcp_list_player_attributes_rsp(struct avctp *conn,
 	if (!avrcp_pull_header(&iov))
 		return FALSE;
 
-	if (!util_iov_pull_u8(&iov, &len))
-		return FALSE;
-
-	len = MIN(len, AVRCP_ATTRIBUTE_LAST);
-
-	for (i = 0; i < len; i++) {
-		uint8_t attr;
-
-		if (!util_iov_pull_u8(&iov, &attr))
-			break;
-
-		/* Don't query invalid attributes */
-		if (attr == AVRCP_ATTRIBUTE_ILLEGAL ||
-					attr > AVRCP_ATTRIBUTE_LAST)
-			continue;
-
-		attrs[count++] = attr;
-	}
-
+	count = avrcp_parse_player_attributes(&iov, attrs, sizeof(attrs));
 	if (!count)
 		return FALSE;
 
@@ -2505,34 +2404,38 @@ static void avrcp_list_player_attributes(struct avrcp *session)
 					session);
 }
 
-static void avrcp_parse_attribute_list(struct avrcp_player *player,
+struct parse_attribute_data {
+	struct media_player *mp;
+	struct media_item *item;
+};
+
+static void parse_attribute(const struct avrcp_attribute *attr,
+							void *user_data)
+{
+	struct parse_attribute_data *data = user_data;
+	const char *key;
+
+	if (attr->charset != 106)
+		return;
+
+	key = metadata_to_str(attr->id);
+	if (key == NULL)
+		return;
+
+	media_player_set_metadata(data->mp, data->item, key, attr->value,
+								attr->len);
+}
+
+static void avrcp_player_parse_attributes(struct avrcp_player *player,
 					struct media_item *item,
 					struct iovec *iov, uint8_t count)
 {
-	struct media_player *mp = player->user_data;
+	struct parse_attribute_data data = {
+		.mp = player->user_data,
+		.item = item,
+	};
 
-	for (; count > 0; count--) {
-		uint32_t id;
-		uint16_t charset, len;
-		uint8_t *value;
-
-		if (!util_iov_pull_be32(iov, &id) ||
-				!util_iov_pull_be16(iov, &charset) ||
-				!util_iov_pull_be16(iov, &len))
-			return;
-
-		value = util_iov_pull_mem(iov, len);
-		if (!value)
-			return;
-
-		if (charset == 106) {
-			const char *key = metadata_to_str(id);
-
-			if (key != NULL)
-				media_player_set_metadata(mp, item, key,
-								value, len);
-		}
-	}
+	avrcp_parse_attribute_list(iov, count, parse_attribute, &data);
 }
 
 static void avrcp_abort_continuing(struct avrcp *session, uint8_t pdu_id)
@@ -2589,7 +2492,7 @@ static gboolean avrcp_get_element_attributes_rsp(struct avctp *conn,
 
 	item = media_player_set_playlist_item(mp, player->uid);
 
-	avrcp_parse_attribute_list(player, item, &iov, count);
+	avrcp_player_parse_attributes(player, item, &iov, count);
 
 	media_player_metadata_changed(mp);
 
@@ -2671,57 +2574,28 @@ static const char *subtype_to_string(uint32_t subtype)
 	return "None";
 }
 
-static gboolean parse_media_name(struct iovec *iov, char *name)
-{
-	uint16_t namelen;
-	uint8_t *namebuf;
-
-	if (!util_iov_pull_be16(iov, &namelen))
-		return FALSE;
-
-	namebuf = util_iov_pull_mem(iov, namelen);
-	if (!namebuf)
-		return FALSE;
-
-	namelen = MIN(namelen, NAME_MAX_LEN - 1);
-
-	memset(name, 0, NAME_MAX_LEN);
-	memcpy(name, namebuf, namelen);
-	strtoutf8(name, namelen);
-
-	return TRUE;
-}
-
 static struct media_item *parse_media_element(struct avrcp *session,
 							struct iovec *iov)
 {
 	struct avrcp_player *player;
 	struct media_player *mp;
 	struct media_item *item;
-	char name[NAME_MAX_LEN];
-	uint64_t uid;
-	uint8_t count;
+	struct avrcp_media_element element;
 
-	/* Skip the media type and character set */
-	if (!util_iov_pull_be64(iov, &uid) || !util_iov_pull(iov, 3))
-		return NULL;
-
-	if (!parse_media_name(iov, name))
-		return NULL;
-
-	if (!util_iov_pull_u8(iov, &count))
+	if (!avrcp_parse_media_element(iov, &element))
 		return NULL;
 
 	player = session->controller->player;
 	mp = player->user_data;
 
-	item = media_player_create_item(mp, name, PLAYER_ITEM_TYPE_AUDIO, uid);
+	item = media_player_create_item(mp, element.name,
+					PLAYER_ITEM_TYPE_AUDIO, element.uid);
 	if (item == NULL)
 		return NULL;
 
 	media_item_set_playable(item, true);
 
-	avrcp_parse_attribute_list(player, item, iov, count);
+	avrcp_player_parse_attributes(player, item, iov, element.count);
 
 	return item;
 }
@@ -2732,26 +2606,17 @@ static struct media_item *parse_media_folder(struct avrcp *session,
 	struct avrcp_player *player = session->controller->player;
 	struct media_player *mp = player->user_data;
 	struct media_item *item;
-	char name[NAME_MAX_LEN];
-	uint64_t uid;
-	uint8_t type;
-	uint8_t playable;
+	struct avrcp_media_folder folder;
 
-	/* Skip the character set */
-	if (!util_iov_pull_be64(iov, &uid) ||
-			!util_iov_pull_u8(iov, &type) ||
-			!util_iov_pull_u8(iov, &playable) ||
-			!util_iov_pull(iov, 2))
+	if (!avrcp_parse_media_folder(iov, &folder))
 		return NULL;
 
-	if (!parse_media_name(iov, name))
-		return NULL;
-
-	item = media_player_create_folder(mp, name, type, uid);
+	item = media_player_create_folder(mp, folder.name, folder.type,
+								folder.uid);
 	if (!item)
 		return NULL;
 
-	media_item_set_playable(item, playable & 0x01);
+	media_item_set_playable(item, folder.playable & 0x01);
 
 	return item;
 }
@@ -3043,7 +2908,7 @@ static gboolean avrcp_get_item_attributes_rsp(struct avctp *conn,
 
 	item = media_player_set_playlist_item(mp, player->uid);
 
-	avrcp_parse_attribute_list(player, item, &iov, count);
+	avrcp_player_parse_attributes(player, item, &iov, count);
 
 	media_player_metadata_changed(mp);
 
diff --git a/profiles/audio/avrcp.h b/profiles/audio/avrcp.h
index 21351a4bc422..9f72c2ed4413 100644
--- a/profiles/audio/avrcp.h
+++ b/profiles/audio/avrcp.h
@@ -9,13 +9,7 @@
  *
  */
 
-/* player attributes */
-#define AVRCP_ATTRIBUTE_ILLEGAL		0x00
-#define AVRCP_ATTRIBUTE_EQUALIZER	0x01
-#define AVRCP_ATTRIBUTE_REPEAT_MODE	0x02
-#define AVRCP_ATTRIBUTE_SHUFFLE		0x03
-#define AVRCP_ATTRIBUTE_SCAN		0x04
-#define AVRCP_ATTRIBUTE_LAST		AVRCP_ATTRIBUTE_SCAN
+#include "avrcp-parse.h"
 
 /* equalizer values */
 #define AVRCP_EQUALIZER_OFF		0x01
-- 
2.54.0


  parent reply	other threads:[~2026-09-01 17:53 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-01 17:53 [PATCH BlueZ v1 1/5] avrcp: Fix out-of-bounds parsing of ListPlayerAttributes response Luiz Augusto von Dentz
2026-09-01 17:53 ` [PATCH BlueZ v1 2/5] avrcp: Fix out-of-bounds read parsing attribute lists Luiz Augusto von Dentz
2026-09-01 17:53 ` [PATCH BlueZ v1 3/5] avrcp: Use util_iov helpers to parse responses Luiz Augusto von Dentz
2026-09-01 17:53 ` Luiz Augusto von Dentz [this message]
2026-09-01 17:53 ` [PATCH BlueZ v1 5/5] unit/test-avrcp: Add robustness tests for response parsing Luiz Augusto von Dentz
2026-09-01 21:04 ` [BlueZ,v1,1/5] avrcp: Fix out-of-bounds parsing of ListPlayerAttributes response bluez.test.bot
2026-09-03 13:02 ` [PATCH BlueZ v1 1/5] " Bastien Nocera
2026-09-03 14:32   ` Bastien Nocera

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260901175315.1348621-4-luiz.dentz@gmail.com \
    --to=luiz.dentz@gmail.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox