All of lore.kernel.org
 help / color / mirror / Atom feed
From: Inga Stotland <inga.stotland@intel.com>
To: linux-bluetooth@vger.kernel.org
Cc: brian.gix@intel.com, Inga Stotland <inga.stotland@intel.com>
Subject: [PATCH BlueZ 20/20] tools/mesh-cfgclient: Export configuration database
Date: Wed, 22 Sep 2021 20:26:03 -0700	[thread overview]
Message-ID: <20210923032603.50536-21-inga.stotland@intel.com> (raw)
In-Reply-To: <20210923032603.50536-1-inga.stotland@intel.com>

This adds main menu command "export-db".
When the command is invoked, JSON configuration object is
cloned and trimmed of extraneous properties.
Information about netkeys, appkeys and device keys are obtained
from bluetooth-meshd by calling ExportKeys() method.
The obtained key values are recorded in the export JSON object.
---
 tools/mesh-cfgclient.c | 195 +++++++++++++++++++++++++++++++++++++++++
 tools/mesh/mesh-db.c   | 191 ++++++++++++++++++++++++++++++++++++++++
 tools/mesh/mesh-db.h   |   7 ++
 3 files changed, 393 insertions(+)

diff --git a/tools/mesh-cfgclient.c b/tools/mesh-cfgclient.c
index beeb115dc..237afbb5f 100644
--- a/tools/mesh-cfgclient.c
+++ b/tools/mesh-cfgclient.c
@@ -17,6 +17,7 @@
 #include <ctype.h>
 #include <dbus/dbus.h>
 #include <errno.h>
+#include <libgen.h>
 #include <stdio.h>
 #include <time.h>
 
@@ -51,6 +52,7 @@
 #define MAX_CRPL_SIZE		0x7fff
 
 #define DEFAULT_CFG_FILE	"config_db.json"
+#define DEFAULT_EXPORT_FILE	"export_db.json"
 
 struct meshcfg_el {
 	const char *path;
@@ -835,6 +837,197 @@ static void cmd_scan_unprov(int argc, char *argv[])
 
 }
 
+static uint8_t *parse_key(struct l_dbus_message_iter *iter, uint16_t id,
+							const char *name)
+{
+	uint8_t *val;
+	uint32_t len;
+
+	if (!l_dbus_message_iter_get_fixed_array(iter, &val, &len)
+								|| len != 16) {
+		bt_shell_printf("Failed to parse %s %4.4x\n", name, id);
+		return NULL;
+	}
+
+	return val;
+}
+
+static bool parse_app_keys(struct l_dbus_message_iter *iter, uint16_t net_idx,
+								void *user_data)
+{
+	struct l_dbus_message_iter app_keys, app_key, opts;
+	uint16_t app_idx;
+
+	if (!l_dbus_message_iter_get_variant(iter, "a(qaya{sv})", &app_keys))
+		return false;
+
+	while (l_dbus_message_iter_next_entry(&app_keys, &app_idx, &app_key,
+								&opts)) {
+		struct l_dbus_message_iter var;
+		uint8_t *val, *old_val = NULL;
+		const char *key;
+
+		val = parse_key(&app_key, app_idx, "AppKey");
+		if (!val)
+			return false;
+
+		while (l_dbus_message_iter_next_entry(&opts, &key, &var)) {
+			if (!strcmp(key, "OldKey")) {
+				if (!l_dbus_message_iter_get_variant(&var, "ay",
+								&app_key))
+					return false;
+
+				old_val = parse_key(&app_key, app_idx,
+								"old NetKey");
+
+				if (!old_val)
+					return false;
+			}
+		}
+
+		mesh_db_set_app_key(user_data, net_idx, app_idx, val, old_val);
+	}
+
+	return true;
+}
+
+static bool parse_net_keys(struct l_dbus_message_iter *iter, void *user_data)
+{
+	struct l_dbus_message_iter net_keys, net_key, opts;
+	uint16_t idx;
+
+	if (!l_dbus_message_iter_get_variant(iter, "a(qaya{sv})", &net_keys))
+		return false;
+
+	while (l_dbus_message_iter_next_entry(&net_keys, &idx, &net_key,
+								&opts)) {
+		struct l_dbus_message_iter var;
+		uint8_t *val, *old_val = NULL;
+		uint8_t phase = KEY_REFRESH_PHASE_NONE;
+		const char *key;
+
+		val = parse_key(&net_key, idx, "NetKey");
+		if (!val)
+			return false;
+
+		while (l_dbus_message_iter_next_entry(&opts, &key, &var)) {
+			if (!strcmp(key, "AppKeys")) {
+				if (!parse_app_keys(&var, idx, user_data))
+					return false;
+			} else if (!strcmp(key, "Phase")) {
+				if (!l_dbus_message_iter_get_variant(&var, "y",
+									&phase))
+					return false;
+			} else if (!strcmp(key, "OldKey")) {
+				if (!l_dbus_message_iter_get_variant(&var, "ay",
+								&net_key))
+					return false;
+
+				old_val = parse_key(&net_key, idx,
+								"old NetKey");
+
+				if (!old_val)
+					return false;
+			}
+		}
+
+		mesh_db_set_net_key(user_data, idx, val, old_val, phase);
+	}
+
+	return true;
+}
+
+static bool parse_dev_keys(struct l_dbus_message_iter *iter, void *user_data)
+{
+	struct l_dbus_message_iter keys, dev_key;
+	uint16_t unicast;
+
+	if (!l_dbus_message_iter_get_variant(iter, "a(qay)", &keys))
+		return false;
+
+	while (l_dbus_message_iter_next_entry(&keys, &unicast, &dev_key)) {
+		uint8_t *data;
+
+		data = parse_key(&dev_key, unicast, "Device Key");
+		if (!data)
+			return false;
+
+		mesh_db_set_device_key(user_data, unicast, data);
+	}
+
+	return true;
+}
+
+static void export_keys_reply(struct l_dbus_proxy *proxy,
+				struct l_dbus_message *msg, void *user_data)
+{
+	struct l_dbus_message_iter iter, var;
+	char *cfg_dir = NULL, *fname = NULL;
+	const char *key;
+	bool is_error = true;
+
+	if (l_dbus_message_is_error(msg)) {
+		const char *name;
+
+		l_dbus_message_get_error(msg, &name, NULL);
+		bt_shell_printf("Failed to export keys: %s", name);
+		goto done;
+
+	}
+
+	if (!l_dbus_message_get_arguments(msg, "a{sv}", &iter)) {
+		bt_shell_printf("Malformed ExportKeys reply");
+		goto done;
+	}
+
+	while (l_dbus_message_iter_next_entry(&iter, &key, &var)) {
+		if (!strcmp(key, "NetKeys")) {
+			if (!parse_net_keys(&var, user_data))
+				goto done;
+		} else if (!strcmp(key, "DevKeys")) {
+			if (!parse_dev_keys(&var, user_data))
+				goto done;
+		}
+	}
+
+	is_error = false;
+
+	cfg_dir = l_strdup(cfg_fname);
+	cfg_dir = dirname(cfg_dir);
+
+	fname = l_strdup_printf("%s/%s", cfg_dir, DEFAULT_EXPORT_FILE);
+
+done:
+	if (mesh_db_finish_export(is_error, user_data, fname)) {
+		if (!is_error)
+			bt_shell_printf("Config DB is exported to %s\n", fname);
+	}
+
+	l_free(cfg_dir);
+	l_free(fname);
+}
+
+static void cmd_export_db(int argc, char *argv[])
+{
+	void *cfg_export;
+
+	if (!local || !local->proxy || !local->mgmt_proxy) {
+		bt_shell_printf("Node is not attached\n");
+		return;
+	}
+
+	/* Generate a properly formatted DB from the local config */
+	cfg_export = mesh_db_prepare_export();
+	if (!cfg_export) {
+		bt_shell_printf("Failed to prepare config db\n");
+		return;
+	}
+
+	/* Export the keys from the daemon */
+	l_dbus_proxy_method_call(local->mgmt_proxy, "ExportKeys", NULL,
+					export_keys_reply, cfg_export, NULL);
+}
+
 static void cmd_list_unprov(int argc, char *argv[])
 {
 	bt_shell_printf(COLOR_YELLOW "Unprovisioned devices:\n" COLOR_OFF);
@@ -1395,6 +1588,8 @@ static const struct bt_shell_menu main_menu = {
 			"List remote mesh nodes"},
 	{ "keys", NULL, cmd_keys,
 			"List available keys"},
+	{ "export-db", NULL, cmd_export_db,
+			"Export mesh configuration database"},
 	{ } },
 };
 
diff --git a/tools/mesh/mesh-db.c b/tools/mesh/mesh-db.c
index 262a274c7..12055c1a9 100644
--- a/tools/mesh/mesh-db.c
+++ b/tools/mesh/mesh-db.c
@@ -48,6 +48,13 @@ static struct mesh_db *cfg;
 static const char *bak_ext = ".bak";
 static const char *tmp_ext = ".tmp";
 
+static const char *js_schema = "http://json-schema.org/draft-04/schema#";
+static const char *schema_id = "http://www.bluetooth.com/specifications/"
+				"assigned-numbers/mesh-profile/"
+				"cdb-schema.json#";
+const char *schema_version = "1.0.0";
+
+
 static bool add_string(json_object *jobj, const char *desc, const char *str)
 {
 	json_object *jstring = json_object_new_string(str);
@@ -2412,3 +2419,187 @@ fail:
 
 	return false;
 }
+
+bool mesh_db_set_device_key(void *expt_cfg, uint16_t unicast, uint8_t key[16])
+{
+	json_object *jnode;
+
+	if (!expt_cfg)
+		return false;
+
+	jnode = get_node_by_unicast(expt_cfg, unicast);
+	if (!jnode)
+		return false;
+
+	return add_u8_16(jnode, "deviceKey", key);
+}
+
+bool mesh_db_set_net_key(void *expt_cfg, uint16_t idx, uint8_t key[16],
+					uint8_t *old_key, uint8_t phase)
+{
+	json_object *jarray, *jkey;
+
+	if (!expt_cfg)
+		return false;
+
+	json_object_object_get_ex(expt_cfg, "netKeys", &jarray);
+	if (!jarray || json_object_get_type(jarray) != json_type_array)
+		return false;
+
+	jkey = get_key_object(jarray, idx);
+	if (!jkey)
+		return false;
+
+	if (!write_int(jkey, "phase", phase))
+		return false;
+
+	if (!add_u8_16(jkey, "key", key))
+		return false;
+
+	if (old_key && !(!add_u8_16(jkey, "oldKey", old_key)))
+		return false;
+
+	return true;
+}
+
+
+bool mesh_db_set_app_key(void *expt_cfg, uint16_t net_idx, uint16_t app_idx,
+					uint8_t key[16], uint8_t *old_key)
+{
+	json_object *jarray, *jkey;
+
+	if (!expt_cfg)
+		return false;
+
+	json_object_object_get_ex(expt_cfg, "appKeys", &jarray);
+	if (!jarray || json_object_get_type(jarray) != json_type_array)
+		return false;
+
+	jkey = get_key_object(jarray, app_idx);
+	if (!jkey)
+		return false;
+
+	if (!add_u8_16(jkey, "key", key))
+		return false;
+
+	if (old_key && !(!add_u8_16(jkey, "oldKey", old_key)))
+		return false;
+
+	return true;
+}
+
+void *mesh_db_prepare_export(void)
+{
+	json_object *export = NULL, *jarray;
+
+	if (!cfg || !cfg->jcfg)
+		return false;
+
+	if (json_object_deep_copy(cfg->jcfg, &export, NULL) != 0)
+		return NULL;
+
+	/* Delete token */
+	json_object_object_del(export, "token");
+
+	/* Delete IV index */
+	json_object_object_del(export, "ivIndex");
+
+	/* Scenes are not supported. Just add an empty array */
+	jarray = json_object_new_array();
+	json_object_object_add(export, "scenes", jarray);
+
+	write_bool(export, "partial", false);
+
+	return export;
+}
+
+bool mesh_db_finish_export(bool is_error, void *expt_cfg, const char *fname)
+{
+	FILE *outfile = NULL;
+	const char *str, *hdr;
+	json_object *jhdr = NULL;
+	bool result = false;
+	char *pos;
+
+	uint32_t sz;
+
+	if (!expt_cfg)
+		return false;
+
+	if (is_error) {
+		json_object_put(expt_cfg);
+		return true;
+	}
+
+	if (!fname)
+		goto done;
+
+	outfile = fopen(fname, "w");
+	if (!outfile) {
+		l_error("Failed to save configuration to %s", fname);
+		goto done;
+	}
+
+	jhdr = json_object_new_object();
+	if (!add_string(jhdr, "$schema", js_schema))
+		goto done;
+
+	if (!add_string(jhdr, "id", schema_id))
+		goto done;
+
+	if (!add_string(jhdr, "version", schema_version))
+		goto done;
+
+	hdr = json_object_to_json_string_ext(jhdr, JSON_C_TO_STRING_PRETTY |
+						JSON_C_TO_STRING_NOSLASHESCAPE);
+
+	str = json_object_to_json_string_ext(expt_cfg, JSON_C_TO_STRING_PRETTY |
+						JSON_C_TO_STRING_NOSLASHESCAPE);
+
+	if (!hdr || !str)
+		goto done;
+
+	/*
+	 * Write two strings to the output while stripping closing "}" from the
+	 * header string and opening "{" from the config object.
+	 */
+
+	pos = strrchr(hdr, '}');
+	if (!pos)
+		goto done;
+
+	*pos = '\0';
+
+	pos = strrchr(hdr, '"');
+	if (!pos)
+		goto done;
+
+	pos[1] = ',';
+
+	if (fwrite(hdr, sizeof(char), strlen(hdr), outfile) < strlen(hdr))
+		goto done;
+
+	pos = strchr(str, '{');
+	if (!pos || pos[1] == '\0')
+		goto done;
+
+	pos++;
+
+	sz = strlen(pos);
+
+	if (fwrite(pos, sizeof(char), sz, outfile) < sz)
+		goto done;
+
+	result = true;
+
+done:
+	if (outfile)
+		fclose(outfile);
+
+	json_object_put(expt_cfg);
+
+	if (jhdr)
+		json_object_put(jhdr);
+
+	return result;
+}
diff --git a/tools/mesh/mesh-db.h b/tools/mesh/mesh-db.h
index 16c46c046..4b6b2adb3 100644
--- a/tools/mesh/mesh-db.h
+++ b/tools/mesh/mesh-db.h
@@ -82,3 +82,10 @@ struct l_queue *mesh_db_load_groups(void);
 bool mesh_db_add_group(struct mesh_group *grp);
 bool mesh_db_add_rejected_addr(uint16_t unicast, uint32_t iv_index);
 bool mesh_db_clear_rejected(uint32_t iv_index);
+bool mesh_db_set_device_key(void *expt_cfg, uint16_t unicast, uint8_t key[16]);
+bool mesh_db_set_net_key(void *expt_cfg, uint16_t idx, uint8_t key[16],
+					uint8_t *old_key, uint8_t phase);
+bool mesh_db_set_app_key(void *expt_cfg, uint16_t net_idx, uint16_t app_idx,
+					uint8_t key[16], uint8_t *old_key);
+void *mesh_db_prepare_export(void);
+bool mesh_db_finish_export(bool is_error, void *expt_cfg, const char *fname);
-- 
2.31.1


  parent reply	other threads:[~2021-09-23  3:27 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-09-23  3:25 [PATCH BlueZ 00/20] Mesh Configuration Database Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 01/20] tools/mesh-cfgclient: Save provisioner info Inga Stotland
2021-09-23  4:00   ` Mesh Configuration Database bluez.test.bot
2021-09-23  4:14     ` Tedd Ho-Jeong An
2021-09-23 16:52   ` bluez.test.bot
2021-09-23  3:25 ` [PATCH BlueZ 02/20] tools/mesh-cfgclient: Add timestamp to config database Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 03/20] tools/mesh-cfgclient: Update stored NetKey and AppKey Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 04/20] tools/mesh-cfgclient: Keep track of updated keys Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 05/20] tools/mesh: Add new info to stored remote nodes Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 06/20] tools/mesh-cfgclient: Overwrite config values when adding new ones Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 07/20] tools/mesh-cfgclient: Store remote node's model bindings Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 08/20] tools/mesh-cfgclient: Store remote node's model subs Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 09/20] tools/mesh-cfgclient: Disallow model commands w/o composition Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 10/20] tools/mesh-cfgclient: Store remote's model publication info Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 11/20] tools/mesh-cfgclient: Check the result of config save Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 12/20] tools/mesh-cfgclient: Rename mesh-db APIs for consistency Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 13/20] tools/mesh-cfgclient: Save remote node feature setting Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 14/20] tools/mesh-cfgclient: Store remote's heartbeat sub/pub Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 15/20] tools/mesh-cfgclient: Add group parent address for DB compliance Inga Stotland
2021-09-23  3:25 ` [PATCH BlueZ 16/20] doc/mesh-api: Add ExportKeys call Inga Stotland
2021-09-23  3:26 ` [PATCH BlueZ 17/20] mesh: Implement ExportKeys() method Inga Stotland
2021-09-23  3:26 ` [PATCH BlueZ 18/20] tools/mesh-cfgclient: Store UUIDs in standard format Inga Stotland
2021-09-23  3:26 ` [PATCH BlueZ 19/20] tools/mesh-cfgclient: Excluded addresses property Inga Stotland
2021-09-23  3:26 ` Inga Stotland [this message]
2021-09-27 20:27 ` [PATCH BlueZ 00/20] Mesh Configuration Database 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=20210923032603.50536-21-inga.stotland@intel.com \
    --to=inga.stotland@intel.com \
    --cc=brian.gix@intel.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.