@@ -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"},
{ } },
};
@@ -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;
+}
@@ -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);