Linux-Bluetooth Archive on lore.kernel.org
 help / color / Atom feed
From: Inga Stotland <inga.stotland@intel.com>
To: linux-bluetooth@vger.kernel.org
Cc: brian.gix@intel.com, michal.lowas-rzechonek@silvair.com,
	Inga Stotland <inga.stotland@intel.com>
Subject: [PATCH BlueZ v2 2/4] mesh: Check app model settings of pub/sub support
Date: Tue, 30 Jun 2020 11:56:15 -0700
Message-ID: <20200630185617.14755-3-inga.stotland@intel.com> (raw)
In-Reply-To: <20200630185617.14755-1-inga.stotland@intel.com>

This adds handling of new options dictionary included with
"Models" and "VendorModels" properties on org.bluez.mesh.Element1
interface.

Supported (optional) dictionary entries:
"Publish" - indicates whether the model supports publication mechanism.
                If not present, publication is enabled.
"Subscribe" - indicates whether the model supports subscription mechanism.
                If not present, subscriptions are enabled.

If a config message related to subscription state is received for a model
that does not support subscription mechanism, an error code 0x08,
("Not A Subscribe Model") is sent in response.

If a config message related to publication state is received for a model
that does not support publication mechanism, an error code 0x07
("Invalid Publish Parameters") is sent in response.
---
 mesh/mesh-config-json.c |  76 +++++++++++++++++-
 mesh/mesh-config.h      |   8 ++
 mesh/model.c            |  98 +++++++++++++++++++----
 mesh/model.h            |   6 ++
 mesh/node.c             | 168 ++++++++++++++++++++++++++++++++--------
 5 files changed, 309 insertions(+), 47 deletions(-)

diff --git a/mesh/mesh-config-json.c b/mesh/mesh-config-json.c
index 05b2a5651..661775f95 100644
--- a/mesh/mesh-config-json.c
+++ b/mesh/mesh-config-json.c
@@ -1096,6 +1096,16 @@ static bool parse_models(json_object *jmodels, struct mesh_config_element *ele)
 				goto fail;
 		}
 
+		if (json_object_object_get_ex(jmodel, "pubEnabled", &jvalue))
+			mod->pub_enabled = json_object_get_boolean(jvalue);
+		else
+			mod->pub_enabled = true;
+
+		if (json_object_object_get_ex(jmodel, "subEnabled", &jvalue))
+			mod->sub_enabled = json_object_get_boolean(jvalue);
+		else
+			mod->sub_enabled = true;
+
 		if (json_object_object_get_ex(jmodel, "publish", &jvalue)) {
 			mod->pub = parse_model_publication(jvalue);
 			if (!mod->pub)
@@ -1562,7 +1572,7 @@ bool mesh_config_write_iv_index(struct mesh_config *cfg, uint32_t idx,
 static void add_model(void *a, void *b)
 {
 	struct mesh_config_model *mod = a;
-	json_object *jmodels = b, *jmodel;
+	json_object *jmodels = b, *jmodel, *jval;
 
 	jmodel = json_object_new_object();
 	if (!jmodel)
@@ -1574,6 +1584,12 @@ static void add_model(void *a, void *b)
 	else
 		write_uint32_hex(jmodel, "modelId", mod->id);
 
+	jval = json_object_new_boolean(mod->sub_enabled);
+	json_object_object_add(jmodel, "subEnabled", jval);
+
+	jval = json_object_new_boolean(mod->pub_enabled);
+	json_object_object_add(jmodel, "pubEnabled", jval);
+
 	json_object_array_add(jmodels, jmodel);
 }
 
@@ -1974,6 +1990,64 @@ bool mesh_config_model_sub_del_all(struct mesh_config *cfg, uint16_t addr,
 	return save_config(cfg->jnode, cfg->node_dir_path);
 }
 
+bool mesh_config_model_pub_enable(struct mesh_config *cfg, uint16_t ele_addr,
+						uint32_t mod_id, bool vendor,
+						bool enable)
+{
+	json_object *jmodel, *jval;
+	int ele_idx;
+
+	if (!cfg)
+		return false;
+
+	ele_idx = get_element_index(cfg->jnode, ele_addr);
+	if (ele_idx < 0)
+		return false;
+
+	jmodel = get_element_model(cfg->jnode, ele_idx, mod_id, vendor);
+	if (!jmodel)
+		return false;
+
+	json_object_object_del(jmodel, "pubDisabled");
+
+	jval = json_object_new_boolean(!enable);
+	json_object_object_add(jmodel, "pubDisabled", jval);
+
+	if (!enable)
+		json_object_object_del(jmodel, "publish");
+
+	return save_config(cfg->jnode, cfg->node_dir_path);
+}
+
+bool mesh_config_model_sub_enable(struct mesh_config *cfg, uint16_t ele_addr,
+						uint32_t mod_id, bool vendor,
+						bool enable)
+{
+	json_object *jmodel, *jval;
+	int ele_idx;
+
+	if (!cfg)
+		return false;
+
+	ele_idx = get_element_index(cfg->jnode, ele_addr);
+	if (ele_idx < 0)
+		return false;
+
+	jmodel = get_element_model(cfg->jnode, ele_idx, mod_id, vendor);
+	if (!jmodel)
+		return false;
+
+	json_object_object_del(jmodel, "subEnabled");
+
+	jval = json_object_new_boolean(enable);
+	json_object_object_add(jmodel, "subEnabled", jval);
+
+	if (!enable)
+		json_object_object_del(jmodel, "subscribe");
+
+	return save_config(cfg->jnode, cfg->node_dir_path);
+}
+
 bool mesh_config_write_seq_number(struct mesh_config *cfg, uint32_t seq,
 								bool cache)
 {
diff --git a/mesh/mesh-config.h b/mesh/mesh-config.h
index 8ff7b63c7..9f30e693b 100644
--- a/mesh/mesh-config.h
+++ b/mesh/mesh-config.h
@@ -45,6 +45,8 @@ struct mesh_config_model {
 	uint16_t *bindings;
 	uint32_t id;
 	bool vendor;
+	bool sub_enabled;
+	bool pub_enabled;
 	uint32_t num_bindings;
 	uint32_t num_subs;
 };
@@ -156,6 +158,12 @@ bool mesh_config_model_sub_del(struct mesh_config *cfg, uint16_t ele_addr,
 						struct mesh_config_sub *sub);
 bool mesh_config_model_sub_del_all(struct mesh_config *cfg, uint16_t ele_addr,
 						uint32_t mod_id, bool vendor);
+bool mesh_config_model_pub_enable(struct mesh_config *cfg, uint16_t ele_addr,
+						uint32_t mod_id, bool vendor,
+						bool enable);
+bool mesh_config_model_sub_enable(struct mesh_config *cfg, uint16_t ele_addr,
+						uint32_t mod_id, bool vendor,
+						bool enable);
 bool mesh_config_app_key_add(struct mesh_config *cfg, uint16_t net_idx,
 				uint16_t app_idx, const uint8_t key[16]);
 bool mesh_config_app_key_update(struct mesh_config *cfg, uint16_t app_idx,
diff --git a/mesh/model.c b/mesh/model.c
index 5ed95afac..afac6ec69 100644
--- a/mesh/model.c
+++ b/mesh/model.c
@@ -51,6 +51,8 @@ struct mesh_model {
 	struct l_queue *subs;
 	struct l_queue *virtuals;
 	struct mesh_model_pub *pub;
+	bool sub_enabled;
+	bool pub_enabled;
 	uint32_t id;
 	uint8_t ele_idx;
 };
@@ -1097,7 +1099,7 @@ int mesh_model_pub_set(struct mesh_node *node, uint16_t addr, uint32_t id,
 	if (!mod)
 		return status;
 
-	if (id == CONFIG_SRV_MODEL || id == CONFIG_CLI_MODEL)
+	if (!mod->pub_enabled || (mod->cbs && !(mod->cbs->pub)))
 		return MESH_STATUS_INVALID_PUB_PARAM;
 
 	if (!appkey_have_key(node_get_net(node), idx))
@@ -1134,9 +1136,11 @@ int mesh_model_pub_set(struct mesh_node *node, uint16_t addr, uint32_t id,
 		/* External model */
 		config_update_model_pub_period(node, mod->ele_idx, id,
 						pub_period_to_ms(period));
-	else
+	else {
 		/* Internal model, call registered callbacks */
-		mod->cbs->pub(mod->pub);
+		if (mod->cbs->pub)
+			mod->cbs->pub(mod->pub);
+	}
 
 	return MESH_STATUS_SUCCESS;
 }
@@ -1150,6 +1154,11 @@ struct mesh_model_pub *mesh_model_pub_get(struct mesh_node *node, uint16_t addr,
 	if (!mod)
 		return NULL;
 
+	if (!mod->pub_enabled || (mod->cbs && !(mod->cbs->pub)))
+		*status = MESH_STATUS_INVALID_PUB_PARAM;
+	else
+		*status = MESH_STATUS_SUCCESS;
+
 	return mod->pub;
 }
 
@@ -1171,6 +1180,13 @@ struct mesh_model *mesh_model_new(uint8_t ele_idx, uint32_t id)
 	mod->id = id;
 	mod->ele_idx = ele_idx;
 	mod->virtuals = l_queue_new();
+
+	/*
+	 * Unless specifically indicated by an app, subscriptions and
+	 * publications are enabled by default
+	 */
+	mod->sub_enabled = true;
+	mod->pub_enabled = true;
 	return mod;
 }
 
@@ -1318,6 +1334,9 @@ int mesh_model_sub_get(struct mesh_node *node, uint16_t addr, uint32_t id,
 	if (!mod)
 		return status;
 
+	if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub)))
+		return MESH_STATUS_NOT_SUB_MOD;
+
 	entry = l_queue_get_entries(mod->subs);
 	*size = 0;
 	n = 0;
@@ -1358,6 +1377,9 @@ int mesh_model_sub_add(struct mesh_node *node, uint16_t addr, uint32_t id,
 	if (!mod)
 		return status;
 
+	if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub)))
+		return MESH_STATUS_NOT_SUB_MOD;
+
 	status = add_sub(node_get_net(node), mod, group, is_virt, dst);
 
 	if (status != MESH_STATUS_SUCCESS)
@@ -1381,6 +1403,9 @@ int mesh_model_sub_ovr(struct mesh_node *node, uint16_t addr, uint32_t id,
 	if (!mod)
 		return status;
 
+	if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub)))
+		return MESH_STATUS_NOT_SUB_MOD;
+
 	subs = mod->subs;
 	virtuals = mod->virtuals;
 	mod->subs = l_queue_new();
@@ -1430,6 +1455,9 @@ int mesh_model_sub_del(struct mesh_node *node, uint16_t addr, uint32_t id,
 	if (!mod)
 		return status;
 
+	if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub)))
+		return MESH_STATUS_NOT_SUB_MOD;
+
 	if (is_virt) {
 		struct mesh_virtual *virt;
 
@@ -1448,27 +1476,22 @@ int mesh_model_sub_del(struct mesh_node *node, uint16_t addr, uint32_t id,
 
 	*dst = grp;
 
-	if (l_queue_remove(mod->subs, L_UINT_TO_PTR(grp)))
+	if (l_queue_remove(mod->subs, L_UINT_TO_PTR(grp))) {
 		mesh_net_dst_unreg(node_get_net(node), grp);
 
-	if (!mod->cbs)
-		/* External models */
-		config_update_model_subscriptions(node, mod);
+		if (!mod->cbs)
+			/* External models */
+			config_update_model_subscriptions(node, mod);
+	}
 
 	return MESH_STATUS_SUCCESS;
 }
 
-int mesh_model_sub_del_all(struct mesh_node *node, uint16_t addr, uint32_t id)
+static void remove_subs(struct mesh_node *node, struct mesh_model *mod)
 {
-	int status;
-	struct mesh_model *mod;
 	const struct l_queue_entry *entry;
 	struct mesh_net *net = node_get_net(node);
 
-	mod = find_model(node, addr, id, &status);
-	if (!mod)
-		return status;
-
 	entry = l_queue_get_entries(mod->subs);
 
 	for (; entry; entry = entry->next)
@@ -1476,6 +1499,21 @@ int mesh_model_sub_del_all(struct mesh_node *node, uint16_t addr, uint32_t id)
 
 	l_queue_clear(mod->subs, NULL);
 	l_queue_clear(mod->virtuals, unref_virt);
+}
+
+int mesh_model_sub_del_all(struct mesh_node *node, uint16_t addr, uint32_t id)
+{
+	int status;
+	struct mesh_model *mod;
+
+	mod = find_model(node, addr, id, &status);
+	if (!mod)
+		return status;
+
+	if (!mod->sub_enabled || (mod->cbs && !(mod->cbs->sub)))
+		return MESH_STATUS_NOT_SUB_MOD;
+
+	remove_subs(node, mod);
 
 	if (!mod->cbs)
 		/* External models */
@@ -1677,6 +1715,38 @@ void model_build_config(void *model, void *msg_builder)
 	l_dbus_message_builder_leave_struct(builder);
 }
 
+void mesh_model_enable_pub(struct mesh_model *mod, bool enable)
+{
+	mod->pub_enabled = enable;
+
+	if (!mod->pub_enabled && mod->pub) {
+		if (mod->pub->virt)
+			unref_virt(mod->pub->virt);
+
+		l_free(mod->pub);
+		mod->pub = NULL;
+	}
+}
+
+bool mesh_model_is_pub_enabled(struct mesh_model *mod)
+{
+	return mod->pub_enabled;
+}
+
+void mesh_model_enable_sub(struct mesh_node *node, struct mesh_model *mod,
+								bool enable)
+{
+	mod->sub_enabled = enable;
+
+	if (!mod->sub_enabled)
+		remove_subs(node, mod);
+}
+
+bool mesh_model_is_sub_enabled(struct mesh_model *mod)
+{
+	return mod->sub_enabled;
+}
+
 void mesh_model_init(void)
 {
 	mesh_virtuals = l_queue_new();
diff --git a/mesh/model.h b/mesh/model.h
index f8e0f9d37..f717fb00c 100644
--- a/mesh/model.h
+++ b/mesh/model.h
@@ -112,5 +112,11 @@ bool mesh_model_opcode_get(const uint8_t *buf, uint16_t size, uint32_t *opcode,
 								uint16_t *n);
 void model_build_config(void *model, void *msg_builder);
 
+void mesh_model_enable_pub(struct mesh_model *mod, bool enable);
+bool mesh_model_is_pub_enabled(struct mesh_model *mod);
+void mesh_model_enable_sub(struct mesh_node *node, struct mesh_model *mod,
+								bool enable);
+bool mesh_model_is_sub_enabled(struct mesh_model *mod);
+
 void mesh_model_init(void);
 void mesh_model_cleanup(void);
diff --git a/mesh/node.c b/mesh/node.c
index ee6d1833f..9f0f15070 100644
--- a/mesh/node.c
+++ b/mesh/node.c
@@ -386,6 +386,12 @@ static bool add_models_from_storage(struct mesh_node *node,
 		if (!mod)
 			return false;
 
+		if (!db_mod->pub_enabled)
+			mesh_model_enable_pub(mod, false);
+
+		if (!db_mod->sub_enabled)
+			mesh_model_enable_sub(node, mod, false);
+
 		l_queue_insert(ele->models, mod, compare_model_id, NULL);
 	}
 
@@ -1041,65 +1047,99 @@ static void app_disc_cb(struct l_dbus *bus, void *user_data)
 	free_node_dbus_resources(node);
 }
 
-static bool get_sig_models_from_properties(struct node_element *ele,
+static bool get_model_options(struct mesh_node *node, struct mesh_model *mod,
+					struct l_dbus_message_iter *opts)
+{
+	const char *key;
+	struct l_dbus_message_iter var;
+	bool opt;
+
+	while (l_dbus_message_iter_next_entry(opts, &key, &var)) {
+
+		if (!strcmp(key, "Publish")) {
+			if (!l_dbus_message_iter_get_variant(&var, "b", &opt))
+				return false;
+			mesh_model_enable_pub(mod, opt);
+		} else if (!strcmp(key, "Subscribe")) {
+			if (!l_dbus_message_iter_get_variant(&var, "b", &opt))
+				return false;
+			mesh_model_enable_sub(node, mod, opt);
+		} else
+			return false;
+	}
+
+	return true;
+}
+
+static bool generate_model(struct mesh_node *node, struct node_element *ele,
+				uint32_t id, struct l_dbus_message_iter *opts)
+{
+	struct mesh_model *mod;
+
+	/* Disallow duplicates */
+	if (l_queue_find(ele->models, match_model_id,
+			 L_UINT_TO_PTR(id)))
+		return false;
+
+	mod = mesh_model_new(ele->idx, id);
+
+	if (!get_model_options(node, mod, opts)) {
+		l_free(mod);
+		return false;
+	}
+
+	l_queue_insert(ele->models, mod, compare_model_id, NULL);
+
+	return true;
+}
+
+static bool get_sig_models_from_properties(struct mesh_node *node,
+					struct node_element *ele,
 					struct l_dbus_message_iter *property)
 {
-	struct l_dbus_message_iter ids;
-	uint16_t mod_id;
+	struct l_dbus_message_iter mods, var;
+	uint16_t m_id;
 
 	if (!ele->models)
 		ele->models = l_queue_new();
 
-	if (!l_dbus_message_iter_get_variant(property, "aq", &ids))
+	if (!l_dbus_message_iter_get_variant(property, "a(qa{sv})", &mods))
 		return false;
 
 	/* Bluetooth SIG defined models */
-	while (l_dbus_message_iter_next_entry(&ids, &mod_id)) {
-		struct mesh_model *mod;
-		uint32_t id = mod_id | VENDOR_ID_MASK;
+	while (l_dbus_message_iter_next_entry(&mods, &m_id, &var)) {
+		uint32_t id = m_id | VENDOR_ID_MASK;
 
 		/* Allow Config Server Model only on the primary element */
 		if (ele->idx != PRIMARY_ELE_IDX && id == CONFIG_SRV_MODEL)
 			return false;
 
-		/* Disallow duplicates */
-		if (l_queue_find(ele->models, match_model_id,
-						L_UINT_TO_PTR(id)))
+		if (!generate_model(node, ele, id, &var))
 			return false;
-
-		mod = mesh_model_new(ele->idx, id);
-
-		l_queue_insert(ele->models, mod, compare_model_id, NULL);
 	}
 
 	return true;
 }
 
-static bool get_vendor_models_from_properties(struct node_element *ele,
+static bool get_vendor_models_from_properties(struct mesh_node *node,
+					struct node_element *ele,
 					struct l_dbus_message_iter *property)
 {
-	struct l_dbus_message_iter ids;
-	uint16_t mod_id, vendor_id;
+	struct l_dbus_message_iter mods, var;
+	uint16_t m_id, v_id;
 
 	if (!ele->models)
 		ele->models = l_queue_new();
 
-	if (!l_dbus_message_iter_get_variant(property, "a(qq)", &ids))
+	if (!l_dbus_message_iter_get_variant(property, "a(qqa{sv})", &mods))
 		return false;
 
 	/* Vendor defined models */
-	while (l_dbus_message_iter_next_entry(&ids, &vendor_id, &mod_id)) {
-		struct mesh_model *mod;
-		uint32_t id = mod_id | (vendor_id << 16);
+	while (l_dbus_message_iter_next_entry(&mods, &v_id, &m_id, &var)) {
+		uint32_t id = m_id | (v_id << 16);
 
-		/* Disallow duplicates */
-		if (l_queue_find(ele->models, match_model_id,
-							L_UINT_TO_PTR(id)))
+		if (!generate_model(node, ele, id, &var))
 			return false;
-
-		mod = mesh_model_new(ele->idx, id);
-
-		l_queue_insert(ele->models, mod, compare_model_id, NULL);
 	}
 
 	return true;
@@ -1130,14 +1170,19 @@ static bool get_element_properties(struct mesh_node *node, const char *path,
 
 		} else if (!strcmp(key, "Models")) {
 
-			if (mods || !get_sig_models_from_properties(ele, &var))
+			if (mods)
+				goto fail;
+
+			if (!get_sig_models_from_properties(node, ele, &var))
 				goto fail;
 
 			mods = true;
 		} else if (!strcmp(key, "VendorModels")) {
 
-			if (vendor_mods ||
-				!get_vendor_models_from_properties(ele, &var))
+			if (vendor_mods)
+				goto fail;
+
+			if (!get_vendor_models_from_properties(node, ele, &var))
 				goto fail;
 
 			vendor_mods = true;
@@ -1225,7 +1270,8 @@ static void convert_node_to_storage(struct mesh_node *node,
 			db_mod->id = mod_id;
 			db_mod->vendor = ((mod_id & VENDOR_ID_MASK)
 							!= VENDOR_ID_MASK);
-
+			db_mod->pub_enabled = mesh_model_is_pub_enabled(mod);
+			db_mod->sub_enabled = mesh_model_is_sub_enabled(mod);
 			l_queue_push_tail(db_ele->models, db_mod);
 		}
 		l_queue_push_tail(db_node->elements, db_ele);
@@ -1381,6 +1427,63 @@ static void update_composition(struct mesh_node *node, struct mesh_node *attach)
 	attach->comp = node->comp;
 }
 
+static void update_model_options(struct mesh_node *node,
+						struct mesh_node *attach)
+{
+	uint32_t len, i;
+	struct node_element *ele, *ele_attach;
+
+	len = l_queue_length(node->elements);
+
+	for (i = 0; i < len; i++) {
+		const struct l_queue_entry *entry;
+
+		ele = l_queue_find(node->elements, match_element_idx,
+							L_UINT_TO_PTR(i));
+		ele_attach = l_queue_find(attach->elements, match_element_idx,
+							L_UINT_TO_PTR(i));
+		if (!ele || !ele_attach)
+			continue;
+
+		entry = l_queue_get_entries(ele->models);
+
+		for (; entry; entry = entry->next) {
+			struct mesh_model *mod, *updated_mod = entry->data;
+			uint32_t id = mesh_model_get_model_id(updated_mod);
+			bool opt, updated_opt;
+			bool vendor = id < VENDOR_ID_MASK;
+
+			mod = l_queue_find(ele_attach->models, match_model_id,
+							L_UINT_TO_PTR(id));
+			if (!mod)
+				continue;
+
+			if (!vendor)
+				id &= ~VENDOR_ID_MASK;
+
+			opt = mesh_model_is_pub_enabled(mod);
+			updated_opt = mesh_model_is_pub_enabled(updated_mod);
+
+			if (updated_opt != opt) {
+				mesh_model_enable_pub(mod, updated_opt);
+				mesh_config_model_pub_enable(attach->cfg,
+							attach->primary + i, id,
+							vendor, updated_opt);
+			}
+
+			opt = mesh_model_is_sub_enabled(mod);
+			updated_opt = mesh_model_is_sub_enabled(updated_mod);
+
+			if (updated_opt != opt) {
+				mesh_model_enable_sub(node, mod, updated_opt);
+				mesh_config_model_sub_enable(attach->cfg,
+							attach->primary + i, id,
+							vendor, updated_opt);
+			}
+		}
+	}
+}
+
 static bool check_req_node(struct managed_obj_request *req)
 {
 	uint8_t node_comp[MAX_MSG_LEN - 2];
@@ -1452,6 +1555,7 @@ static bool attach_req_node(struct mesh_node *attach, struct mesh_node *node)
 	node->owner = NULL;
 
 	update_composition(node, attach);
+	update_model_options(node, attach);
 
 	node_remove(node);
 
-- 
2.26.2


  parent reply index

Thread overview: 6+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-06-30 18:56 [PATCH BlueZ v2 0/4] Add options to Models and VendorModels Inga Stotland
2020-06-30 18:56 ` [PATCH BlueZ v2 1/4] doc/mesh-api: Add dictionary to model properties Inga Stotland
2020-06-30 18:56 ` Inga Stotland [this message]
2020-06-30 18:56 ` [PATCH BlueZ v2 3/4] tools/mesh-cfgclient: Add options to "Models" property Inga Stotland
2020-06-30 18:56 ` [PATCH BlueZ v2 4/4] test/test-mesh: " Inga Stotland
2020-07-01 19:11 ` [PATCH BlueZ v2 0/4] Add options to Models and VendorModels Gix, Brian

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=20200630185617.14755-3-inga.stotland@intel.com \
    --to=inga.stotland@intel.com \
    --cc=brian.gix@intel.com \
    --cc=linux-bluetooth@vger.kernel.org \
    --cc=michal.lowas-rzechonek@silvair.com \
    /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

Linux-Bluetooth Archive on lore.kernel.org

Archives are clonable:
	git clone --mirror https://lore.kernel.org/linux-bluetooth/0 linux-bluetooth/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 linux-bluetooth linux-bluetooth/ https://lore.kernel.org/linux-bluetooth \
		linux-bluetooth@vger.kernel.org
	public-inbox-index linux-bluetooth

Example config snippet for mirrors

Newsgroup available over NNTP:
	nntp://nntp.lore.kernel.org/org.kernel.vger.linux-bluetooth


AGPL code for this site: git clone https://public-inbox.org/public-inbox.git