You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by GitBox <gi...@apache.org> on 2018/07/18 08:35:24 UTC

[GitHub] michal-narajowski closed pull request #142: Mesh sync with Zephyr (persistent storage)

michal-narajowski closed pull request #142: Mesh sync with Zephyr (persistent storage)
URL: https://github.com/apache/mynewt-nimble/pull/142
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/apps/blemesh/src/main.c b/apps/blemesh/src/main.c
index 105ec6f9..784001fc 100644
--- a/apps/blemesh/src/main.c
+++ b/apps/blemesh/src/main.c
@@ -136,7 +136,7 @@ fault_test(struct bt_mesh_model *model, uint8_t test_id, uint16_t company_id)
 
     recent_test_id = test_id;
     has_reg_fault = true;
-    bt_mesh_fault_update(model->elem);
+    bt_mesh_fault_update(bt_mesh_model_elem(model));
 
     return 0;
 }
@@ -405,6 +405,14 @@ blemesh_on_sync(void)
 #endif
 
     console_printf("Mesh initialized\n");
+
+    if (IS_ENABLED(CONFIG_SETTINGS)) {
+        settings_load();
+    }
+
+    if (bt_mesh_is_provisioned()) {
+        printk("Mesh network restored from flash\n");
+    }
 }
 
 int
diff --git a/apps/blemesh/syscfg.yml b/apps/blemesh/syscfg.yml
index c109e6ea..b8bdb651 100644
--- a/apps/blemesh/syscfg.yml
+++ b/apps/blemesh/syscfg.yml
@@ -46,3 +46,6 @@ syscfg.vals:
     BLE_MESH_DEBUG_LOW_POWER: 1
     BLE_MESH_DEBUG_FRIEND: 1
     BLE_MESH_DEBUG_PROXY: 1
+
+    BLE_MESH_SETTINGS: 0
+    CONFIG_NFFS: 0
diff --git a/apps/blemesh_light/src/main.c b/apps/blemesh_light/src/main.c
index 4bab1ed3..76bd5222 100755
--- a/apps/blemesh_light/src/main.c
+++ b/apps/blemesh_light/src/main.c
@@ -98,6 +98,14 @@ blemesh_on_sync(void)
 
     console_printf("Mesh initialized\n");
 
+    if (IS_ENABLED(CONFIG_SETTINGS)) {
+        settings_load();
+    }
+
+    if (bt_mesh_is_provisioned()) {
+        printk("Mesh network restored from flash\n");
+    }
+
     /* Hack for demo purposes */
     bt_test_shell_init();
 }
diff --git a/apps/blemesh_light/syscfg.yml b/apps/blemesh_light/syscfg.yml
index e40f0993..556a844e 100644
--- a/apps/blemesh_light/syscfg.yml
+++ b/apps/blemesh_light/syscfg.yml
@@ -50,6 +50,9 @@ syscfg.vals:
     BLE_MESH_SHELL_MODELS: 1
     BLE_MESH_TESTING: 1
     BLE_MESH_OOB_OUTPUT_ACTIONS: 0
+    BLE_MESH_SETTINGS: 0
+    CONFIG_NFFS: 0
+
     USE_NEOPIXEL: 0
 
 syscfg.vals.BLE_MESH_SHELL_MODELS:
diff --git a/apps/blemesh_shell/syscfg.yml b/apps/blemesh_shell/syscfg.yml
index 07cde3b4..227359c5 100644
--- a/apps/blemesh_shell/syscfg.yml
+++ b/apps/blemesh_shell/syscfg.yml
@@ -50,6 +50,8 @@ syscfg.vals:
     BLE_MESH_TESTING: 1
     BLE_MESH_FRIEND: 1
     BLE_MESH_CFG_CLI: 1
+    BLE_MESH_SETTINGS: 0
+    CONFIG_NFFS: 0
 
     BLE_MESH_DEBUG: 0
     BLE_MESH_DEBUG_NET: 0
diff --git a/nimble/host/mesh/include/mesh/access.h b/nimble/host/mesh/include/mesh/access.h
index f41c8f1a..b31b645a 100644
--- a/nimble/host/mesh/include/mesh/access.h
+++ b/nimble/host/mesh/include/mesh/access.h
@@ -130,6 +130,9 @@ struct bt_mesh_msg_ctx {
 	/** Remote address. */
 	u16_t addr;
 
+	/** Destination address of a received message. Not used for sending. */
+	u16_t recv_dst;
+
 	/** Received TTL value. Not used for sending. */
 	u8_t  recv_ttl:7;
 
@@ -194,8 +197,8 @@ struct bt_mesh_model_op {
  *  @brief Encode transmission count & interval steps.
  *
  *  @param count   Number of retransmissions (first transmission is excluded).
- *  @param int_ms  Interval steps in milliseconds. Must be greater than 0
- *                 and a multiple of 10.
+ *  @param int_ms  Interval steps in milliseconds. Must be greater than 0,
+ *                 less than or equal to 320, and a multiple of 10.
  *
  *  @return Mesh transmit value that can be used e.g. for the default
  *          values of the configuration model data.
@@ -320,8 +323,10 @@ struct bt_mesh_model {
 		} vnd;
 	};
 
-	/* The Element this Model belongs to */
-	struct bt_mesh_elem *elem;
+	/* Internal information, mainly for persistent storage */
+	u8_t  elem_idx;   /* Belongs to Nth element */
+	u8_t  mod_idx;    /* Is the Nth model in the element */
+	u16_t flags;      /* Information about what has changed */
 
 	/* Model Publication */
 	struct bt_mesh_model_pub * const pub;
@@ -384,6 +389,15 @@ int bt_mesh_model_send(struct bt_mesh_model *model,
  */
 int bt_mesh_model_publish(struct bt_mesh_model *model);
 
+/**
+ * @brief Get the element that a model belongs to.
+ *
+ * @param mod  Mesh model.
+ *
+ * @return Pointer to the element that the given model belongs to.
+ */
+struct bt_mesh_elem *bt_mesh_model_elem(struct bt_mesh_model *mod);
+
 /** Node Composition */
 struct bt_mesh_comp {
 	u16_t cid;
diff --git a/nimble/host/mesh/include/mesh/cfg_srv.h b/nimble/host/mesh/include/mesh/cfg_srv.h
index ee652e2f..d1e9928e 100644
--- a/nimble/host/mesh/include/mesh/cfg_srv.h
+++ b/nimble/host/mesh/include/mesh/cfg_srv.h
@@ -30,7 +30,7 @@ struct bt_mesh_cfg_srv {
 	u8_t default_ttl;          /* Default TTL */
 
 	/* Heartbeat Publication */
-	struct {
+	struct bt_mesh_hb_pub {
 		struct k_delayed_work timer;
 
 		u16_t dst;
@@ -42,7 +42,7 @@ struct bt_mesh_cfg_srv {
 	} hb_pub;
 
 	/* Heartbeat Subscription */
-	struct {
+	struct bt_mesh_hb_sub {
 		s64_t  expiry;
 
 		u16_t src;
diff --git a/nimble/host/mesh/include/mesh/glue.h b/nimble/host/mesh/include/mesh/glue.h
index 5ccc2d58..26455e82 100644
--- a/nimble/host/mesh/include/mesh/glue.h
+++ b/nimble/host/mesh/include/mesh/glue.h
@@ -41,6 +41,8 @@
 #include "tinycrypt/cmac_mode.h"
 #include "tinycrypt/ecc_dh.h"
 
+#include "config/config.h"
+
 #define u8_t    uint8_t
 #define s8_t    int8_t
 #define u16_t   uint16_t
@@ -334,9 +336,9 @@ static inline unsigned int find_msb_set(u32_t op)
     return 32 - __builtin_clz(op);
 }
 
-#define CONFIG_BLUETOOTH_MESH_FRIEND        BLE_MESH_FRIEND
 #define CONFIG_BT_MESH_FRIEND               BLE_MESH_FRIEND
 #define CONFIG_BT_MESH_GATT_PROXY           BLE_MESH_GATT_PROXY
+#define CONFIG_BT_MESH_IV_UPDATE_TEST       BLE_MESH_IV_UPDATE_TEST
 #define CONFIG_BT_MESH_LOW_POWER            BLE_MESH_LOW_POWER
 #define CONFIG_BT_MESH_LPN_AUTO             BLE_MESH_LPN_AUTO
 #define CONFIG_BT_MESH_LPN_ESTABLISHMENT    BLE_MESH_LPN_ESTABLISHMENT
@@ -344,6 +346,9 @@ static inline unsigned int find_msb_set(u32_t op)
 #define CONFIG_BT_MESH_PB_GATT              BLE_MESH_PB_GATT
 #define CONFIG_BT_MESH_PROV                 BLE_MESH_PROV
 #define CONFIG_BT_TESTING                   BLE_MESH_TESTING
+#define CONFIG_BT_SETTINGS                  BLE_MESH_SETTINGS
+#define CONFIG_SETTINGS                     BLE_MESH_SETTINGS
+#define BT_SETTINGS                         BLE_MESH_SETTINGS
 
 /* Above flags are used with IS_ENABLED macro */
 #define IS_ENABLED(config) MYNEWT_VAL(config)
@@ -357,6 +362,12 @@ static inline unsigned int find_msb_set(u32_t op)
 #define CONFIG_BT_MESH_MODEL_KEY_COUNT      MYNEWT_VAL(BLE_MESH_MODEL_KEY_COUNT)
 #define CONFIG_BT_MESH_NODE_ID_TIMEOUT      MYNEWT_VAL(BLE_MESH_NODE_ID_TIMEOUT)
 #define CONFIG_BT_MAX_CONN                  MYNEWT_VAL(BLE_MAX_CONNECTIONS)
+#define CONFIG_BT_MESH_SEQ_STORE_RATE       MYNEWT_VAL(BLE_MESH_SEQ_STORE_RATE)
+#define CONFIG_BT_MESH_RPL_STORE_TIMEOUT    MYNEWT_VAL(BLE_MESH_RPL_STORE_TIMEOUT)
+#define CONFIG_BT_MESH_APP_KEY_COUNT        MYNEWT_VAL(BLE_MESH_APP_KEY_COUNT)
+#define CONFIG_BT_MESH_SUBNET_COUNT         MYNEWT_VAL(BLE_MESH_SUBNET_COUNT)
+#define CONFIG_BT_MESH_STORE_TIMEOUT        MYNEWT_VAL(BLE_MESH_STORE_TIMEOUT)
+#define CONFIG_BT_MESH_IVU_DIVIDER          MYNEWT_VAL(BLE_MESH_IVU_DIVIDER)
 #define CONFIG_BT_DEVICE_NAME               "nimble-mesh"
 
 #define printk console_printf
@@ -419,4 +430,19 @@ void net_buf_slist_merge_slist(struct net_buf_slist_t *list,
 			       struct net_buf_slist_t *list_to_append);
 #define NET_BUF_SLIST_FOR_EACH_NODE(head, var) STAILQ_FOREACH(var, head, omp_next)
 
+/**
+ * Load serialized items from registered persistence sources. Handlers for
+ * serialized item subtrees registered earlier will be called for encountered
+ * values.
+ *
+ * @return 0 on success, non-zero on failure.
+ */
+#define settings_load conf_load
+int settings_bytes_from_str(char *val_str, void *vp, int *len);
+char *settings_str_from_bytes(void *vp, int vp_len, char *buf, int buf_len);
+
+#define snprintk snprintf
+#define BT_SETTINGS_SIZE(in_size) ((((((in_size) - 1) / 3) * 4) + 4) + 1)
+#define settings_save_one conf_save_one
+
 #endif
diff --git a/nimble/host/mesh/include/mesh/main.h b/nimble/host/mesh/include/mesh/main.h
index 752878fa..7a759ddb 100644
--- a/nimble/host/mesh/include/mesh/main.h
+++ b/nimble/host/mesh/include/mesh/main.h
@@ -287,15 +287,25 @@ void bt_mesh_reset(void);
  *  @param net_idx  Network Key Index
  *  @param flags    Provisioning Flags
  *  @param iv_index IV Index
- *  @param seq      Sequence Number (0 if newly provisioned).
  *  @param addr     Primary element address
  *  @param dev_key  Device Key
  *
  *  @return Zero on success or (negative) error code otherwise.
  */
 int bt_mesh_provision(const u8_t net_key[16], u16_t net_idx,
-		      u8_t flags, u32_t iv_index, u32_t seq,
-		      u16_t addr, const u8_t dev_key[16]);
+		      u8_t flags, u32_t iv_index, u16_t addr,
+		      const u8_t dev_key[16]);
+
+/** @brief Check if the local node has been provisioned.
+ *
+ *  This API can be used to check if the local node has been provisioned
+ *  or not. It can e.g. be helpful to determine if there was a stored
+ *  network in flash, i.e. if the network was restored after calling
+ *  settings_load().
+ *
+ *  @return True if the node is provisioned. False otherwise.
+ */
+bool bt_mesh_is_provisioned(void);
 
 /** @brief Toggle the IV Update test mode
  *
diff --git a/nimble/host/mesh/include/mesh/mesh.h b/nimble/host/mesh/include/mesh/mesh.h
index 31750d0c..9ba63ef0 100644
--- a/nimble/host/mesh/include/mesh/mesh.h
+++ b/nimble/host/mesh/include/mesh/mesh.h
@@ -19,17 +19,8 @@
 #include "main.h"
 #include "cfg_srv.h"
 #include "health_srv.h"
-
-#if MYNEWT_VAL(BLE_MESH_CFG_CLI)
 #include "cfg_cli.h"
-#endif
-
-#if MYNEWT_VAL(BLE_MESH_HEALTH_CLI)
 #include "health_cli.h"
-#endif
-
-#if MYNEWT_VAL(BLE_MESH_GATT_PROXY)
 #include "proxy.h"
-#endif
 
 #endif /* __BT_MESH_H */
diff --git a/nimble/host/mesh/include/mesh/testing.h b/nimble/host/mesh/include/mesh/testing.h
index 1243384c..7b48772a 100644
--- a/nimble/host/mesh/include/mesh/testing.h
+++ b/nimble/host/mesh/include/mesh/testing.h
@@ -58,7 +58,7 @@ void bt_test_cb_register(struct bt_test_cb *cb);
 void bt_test_cb_unregister(struct bt_test_cb *cb);
 
 u8_t mod_bind(struct bt_mesh_model *model, u16_t key_idx);
-u8_t mod_unbind(struct bt_mesh_model *model, u16_t key_idx);
+u8_t mod_unbind(struct bt_mesh_model *model, u16_t key_idx, bool store);
 int cmd_mesh_init(int argc, char *argv[]);
 
 int bt_test_shell_init(void);
diff --git a/nimble/host/mesh/pkg.yml b/nimble/host/mesh/pkg.yml
index 44063590..c06e1818 100644
--- a/nimble/host/mesh/pkg.yml
+++ b/nimble/host/mesh/pkg.yml
@@ -30,6 +30,8 @@ pkg.deps:
     - "@apache-mynewt-core/kernel/os"
     - "@apache-mynewt-core/util/mem"
     - "@apache-mynewt-core/crypto/tinycrypt"
+    - "@apache-mynewt-core/encoding/base64"
+    - "@apache-mynewt-core/sys/config"
     - nimble
     - nimble/host
 
diff --git a/nimble/host/mesh/src/access.c b/nimble/host/mesh/src/access.c
index 332385ba..b06234a4 100644
--- a/nimble/host/mesh/src/access.c
+++ b/nimble/host/mesh/src/access.c
@@ -161,7 +161,7 @@ static int publish_retransmit(struct bt_mesh_model *mod)
 	};
 	struct bt_mesh_net_tx tx = {
 		.ctx = &ctx,
-		.src = mod->elem->addr,
+		.src = bt_mesh_model_elem(mod)->addr,
 		.xmit = bt_mesh_net_transmit_get(),
 		.friend_cred = pub->cred,
 	};
@@ -242,13 +242,44 @@ static void mod_publish(struct ble_npl_event *work)
 	}
 }
 
+struct bt_mesh_elem *bt_mesh_model_elem(struct bt_mesh_model *mod)
+{
+	return &dev_comp->elem[mod->elem_idx];
+}
+
+struct bt_mesh_model *bt_mesh_model_get(bool vnd, u8_t elem_idx, u8_t mod_idx)
+{
+	struct bt_mesh_elem *elem;
+
+	if (elem_idx >= dev_comp->elem_count) {
+		BT_ERR("Invalid element index %u", elem_idx);
+		return NULL;
+	}
+
+	elem = &dev_comp->elem[elem_idx];
+
+	if (vnd) {
+		if (mod_idx >= elem->vnd_model_count) {
+			BT_ERR("Invalid vendor model index %u", mod_idx);
+			return NULL;
+		}
+
+		return &elem->vnd_models[mod_idx];
+	} else {
+		if (mod_idx >= elem->model_count) {
+			BT_ERR("Invalid SIG model index %u", mod_idx);
+			return NULL;
+		}
+
+		return &elem->models[mod_idx];
+	}
+}
+
 static void mod_init(struct bt_mesh_model *mod, struct bt_mesh_elem *elem,
 		     bool vnd, bool primary, void *user_data)
 {
 	int i;
 
-	mod->elem = elem;
-
 	if (mod->pub) {
 		mod->pub->mod = mod;
 		k_delayed_work_init(&mod->pub->timer, mod_publish);
@@ -259,6 +290,13 @@ static void mod_init(struct bt_mesh_model *mod, struct bt_mesh_elem *elem,
 		mod->keys[i] = BT_MESH_KEY_UNUSED;
 	}
 
+	mod->elem_idx = elem - dev_comp->elem;
+	if (vnd) {
+		mod->mod_idx = mod - elem->vnd_models;
+	} else {
+		mod->mod_idx = mod - elem->models;
+	}
+
 	if (vnd) {
 		return;
 	}
@@ -490,7 +528,7 @@ void bt_mesh_model_recv(struct bt_mesh_net_rx *rx, struct os_mbuf *buf)
 	int i;
 
 	BT_DBG("app_idx 0x%04x src 0x%04x dst 0x%04x", rx->ctx.app_idx,
-	       rx->ctx.addr, rx->dst);
+	       rx->ctx.addr, rx->ctx.recv_dst);
 	BT_DBG("len %u: %s", buf->om_len, bt_hex(buf->om_data, buf->om_len));
 
 	if (get_opcode(buf, &opcode) < 0) {
@@ -503,14 +541,15 @@ void bt_mesh_model_recv(struct bt_mesh_net_rx *rx, struct os_mbuf *buf)
 	for (i = 0; i < dev_comp->elem_count; i++) {
 		struct bt_mesh_elem *elem = &dev_comp->elem[i];
 
-		if (BT_MESH_ADDR_IS_UNICAST(rx->dst)) {
-			if (elem->addr != rx->dst) {
+		if (BT_MESH_ADDR_IS_UNICAST(rx->ctx.recv_dst)) {
+			if (elem->addr != rx->ctx.recv_dst) {
 				continue;
 			}
-		} else if (BT_MESH_ADDR_IS_GROUP(rx->dst) ||
-			   BT_MESH_ADDR_IS_VIRTUAL(rx->dst)) {
-			/* find_op will find the correct model for the group */
-		} else if (i != 0 || !bt_mesh_fixed_group_match(rx->dst)) {
+		} else if (BT_MESH_ADDR_IS_GROUP(rx->ctx.recv_dst) ||
+			   BT_MESH_ADDR_IS_VIRTUAL(rx->ctx.recv_dst)) {
+			/* find_op() will do proper model/group matching */
+		} else if (i != 0 ||
+			   !bt_mesh_fixed_group_match(rx->ctx.recv_dst)) {
 			continue;
 		}
 
@@ -526,7 +565,8 @@ void bt_mesh_model_recv(struct bt_mesh_net_rx *rx, struct os_mbuf *buf)
 			count = elem->vnd_model_count;
 		}
 
-		op = find_op(models, count, rx->dst, rx->ctx.app_idx, opcode, &model);
+		op = find_op(models, count, rx->ctx.recv_dst, rx->ctx.app_idx,
+			     opcode, &model);
 		if (op) {
 			struct net_buf_simple_state state;
 
@@ -611,7 +651,7 @@ int bt_mesh_model_send(struct bt_mesh_model *model,
 	struct bt_mesh_net_tx tx = {
 		.sub = bt_mesh_subnet_get(ctx->net_idx),
 		.ctx = ctx,
-		.src = model->elem->addr,
+		.src = bt_mesh_model_elem(model)->addr,
 		.xmit = bt_mesh_net_transmit_get(),
 		.friend_cred = 0,
 	};
@@ -628,7 +668,7 @@ int bt_mesh_model_publish(struct bt_mesh_model *model)
 	};
 	struct bt_mesh_net_tx tx = {
 		.ctx = &ctx,
-		.src = model->elem->addr,
+		.src = bt_mesh_model_elem(model)->addr,
 		.xmit = bt_mesh_net_transmit_get(),
 	};
 	int err;
diff --git a/nimble/host/mesh/src/access.h b/nimble/host/mesh/src/access.h
index 638b6ff6..5ce7f199 100644
--- a/nimble/host/mesh/src/access.h
+++ b/nimble/host/mesh/src/access.h
@@ -11,6 +11,13 @@
 
 #include "mesh/mesh.h"
 
+/* bt_mesh_model.flags */
+enum {
+	BT_MESH_MOD_BIND_PENDING = BIT(0),
+	BT_MESH_MOD_SUB_PENDING = BIT(1),
+	BT_MESH_MOD_PUB_PENDING = BIT(2),
+};
+
 void bt_mesh_elem_register(struct bt_mesh_elem *elem, u8_t count);
 
 u8_t bt_mesh_elem_count(void);
@@ -42,6 +49,8 @@ u16_t bt_mesh_primary_addr(void);
 
 const struct bt_mesh_comp *bt_mesh_comp_get(void);
 
+struct bt_mesh_model *bt_mesh_model_get(bool vnd, u8_t elem_idx, u8_t mod_idx);
+
 void bt_mesh_model_recv(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
 
 int bt_mesh_comp_register(const struct bt_mesh_comp *comp);
diff --git a/nimble/host/mesh/src/adv.c b/nimble/host/mesh/src/adv.c
index a7ac8383..2c841c3f 100644
--- a/nimble/host/mesh/src/adv.c
+++ b/nimble/host/mesh/src/adv.c
@@ -19,8 +19,8 @@
 #include "mesh/porting.h"
 
 #include "adv.h"
-#include "foundation.h"
 #include "net.h"
+#include "foundation.h"
 #include "beacon.h"
 #include "prov.h"
 #include "proxy.h"
@@ -99,17 +99,20 @@ static inline void adv_send(struct os_mbuf *buf)
 	void *cb_data = BT_MESH_ADV(buf)->cb_data;
 	struct ble_gap_adv_params param = { 0 };
 	u16_t duration, adv_int;
-	struct bt_mesh_adv *adv = BT_MESH_ADV(buf);
 	struct bt_data ad;
 	int err;
 
-	adv_int = max(adv_int_min, adv->adv_int);
-	duration = MESH_SCAN_WINDOW_MS + (adv->count + 1) * (adv_int + 10);
+	adv_int = max(adv_int_min,
+		      BT_MESH_TRANSMIT_INT(BT_MESH_ADV(buf)->xmit));
+	duration = (MESH_SCAN_WINDOW_MS +
+		    ((BT_MESH_TRANSMIT_COUNT(BT_MESH_ADV(buf)->xmit) + 1) *
+		     (adv_int + 10)));
 
-	BT_DBG("buf %p, type %u len %u:", buf, adv->type,
-	       buf->om_len);
+	BT_DBG("type %u om_len %u: %s", BT_MESH_ADV(buf)->type,
+	       buf->om_len, bt_hex(buf->om_data, buf->om_len));
 	BT_DBG("count %u interval %ums duration %ums",
-	       adv->count + 1, adv_int, duration);
+	       BT_MESH_TRANSMIT_COUNT(BT_MESH_ADV(buf)->xmit) + 1, adv_int,
+	       duration);
 
 	ad.type = adv_type[BT_MESH_ADV(buf)->type];
 	ad.data_len = buf->om_len;
@@ -199,8 +202,7 @@ void bt_mesh_adv_update(void)
 struct os_mbuf *bt_mesh_adv_create_from_pool(struct os_mbuf_pool *pool,
 					     bt_mesh_adv_alloc_t get_id,
 					     enum bt_mesh_adv_type type,
-					     u8_t xmit_count, u8_t xmit_int,
-					     s32_t timeout)
+					     u8_t xmit, s32_t timeout)
 {
 	struct bt_mesh_adv *adv;
 	struct os_mbuf *buf;
@@ -216,18 +218,19 @@ struct os_mbuf *bt_mesh_adv_create_from_pool(struct os_mbuf_pool *pool,
 	memset(adv, 0, sizeof(*adv));
 
 	adv->type         = type;
-	adv->count        = xmit_count;
-	adv->adv_int      = xmit_int;
+	adv->xmit         = xmit;
+
 	adv->ref_cnt = 1;
 	ble_npl_event_set_arg(&adv->ev, buf);
+
 	return buf;
 }
 
-struct os_mbuf *bt_mesh_adv_create(enum bt_mesh_adv_type type, u8_t xmit_count,
-				   u8_t xmit_int, s32_t timeout)
+struct os_mbuf *bt_mesh_adv_create(enum bt_mesh_adv_type type, u8_t xmit,
+				   s32_t timeout)
 {
 	return bt_mesh_adv_create_from_pool(&adv_os_mbuf_pool, adv_alloc, type,
-					    xmit_count, xmit_int, timeout);
+					    xmit, timeout);
 }
 
 void bt_mesh_adv_send(struct os_mbuf *buf, const struct bt_mesh_send_cb *cb,
diff --git a/nimble/host/mesh/src/adv.h b/nimble/host/mesh/src/adv.h
index 564e3657..d37e0d41 100644
--- a/nimble/host/mesh/src/adv.h
+++ b/nimble/host/mesh/src/adv.h
@@ -40,8 +40,8 @@ struct bt_mesh_adv {
 
 	u8_t      type:2,
 		  busy:1;
-	u8_t      count:3,
-		  adv_int:5;
+	u8_t      xmit;
+
 	union {
 		/* Address, used e.g. for Friend Queue messages */
 		u16_t addr;
@@ -59,14 +59,13 @@ struct bt_mesh_adv {
 typedef struct bt_mesh_adv *(*bt_mesh_adv_alloc_t)(int id);
 
 /* xmit_count: Number of retransmissions, i.e. 0 == 1 transmission */
-struct os_mbuf * bt_mesh_adv_create(enum bt_mesh_adv_type type, u8_t xmit_count,
-				    u8_t xmit_int, s32_t timeout);
+struct os_mbuf *bt_mesh_adv_create(enum bt_mesh_adv_type type, u8_t xmit,
+				   s32_t timeout);
 
 struct os_mbuf *bt_mesh_adv_create_from_pool(struct os_mbuf_pool *pool,
 					     bt_mesh_adv_alloc_t get_id,
 					     enum bt_mesh_adv_type type,
-					     u8_t xmit_count, u8_t xmit_int,
-					     s32_t timeout);
+					     u8_t xmit, s32_t timeout);
 
 void bt_mesh_adv_send(struct os_mbuf *buf, const struct bt_mesh_send_cb *cb,
 		      void *cb_data);
diff --git a/nimble/host/mesh/src/beacon.c b/nimble/host/mesh/src/beacon.c
index 38990ec8..b7eca32b 100644
--- a/nimble/host/mesh/src/beacon.c
+++ b/nimble/host/mesh/src/beacon.c
@@ -30,12 +30,10 @@
 #define BEACON_TYPE_SECURE         0x01
 
 /* 3 transmissions, 20ms interval */
-#define UNPROV_XMIT_COUNT          2
-#define UNPROV_XMIT_INT            20
+#define UNPROV_XMIT                BT_MESH_TRANSMIT(2, 20)
 
 /* 1 transmission, 20ms interval */
-#define PROV_XMIT_COUNT            0
-#define PROV_XMIT_INT              20
+#define PROV_XMIT                  BT_MESH_TRANSMIT(0, 20)
 
 static struct k_delayed_work beacon_timer;
 
@@ -131,8 +129,8 @@ static int secure_beacon_send(void)
 			continue;
 		}
 
-		buf = bt_mesh_adv_create(BT_MESH_ADV_BEACON, PROV_XMIT_COUNT,
-					 PROV_XMIT_INT, K_NO_WAIT);
+		buf = bt_mesh_adv_create(BT_MESH_ADV_BEACON, PROV_XMIT,
+					 K_NO_WAIT);
 		if (!buf) {
 			BT_ERR("Unable to allocate beacon buffer");
 			return -ENOBUFS;
@@ -157,8 +155,7 @@ static int unprovisioned_beacon_send(void)
 
 	BT_DBG("unprovisioned_beacon_send");
 
-	buf = bt_mesh_adv_create(BT_MESH_ADV_BEACON, UNPROV_XMIT_COUNT,
-				 UNPROV_XMIT_INT, K_NO_WAIT);
+	buf = bt_mesh_adv_create(BT_MESH_ADV_BEACON, UNPROV_XMIT, K_NO_WAIT);
 	if (!buf) {
 		BT_ERR("Unable to allocate beacon buffer");
 		return -ENOBUFS;
@@ -184,8 +181,8 @@ static int unprovisioned_beacon_send(void)
 	if (prov->uri) {
 		size_t len;
 
-		buf = bt_mesh_adv_create(BT_MESH_ADV_URI, UNPROV_XMIT_COUNT,
-					 UNPROV_XMIT_INT, K_NO_WAIT);
+		buf = bt_mesh_adv_create(BT_MESH_ADV_URI, UNPROV_XMIT,
+					 K_NO_WAIT);
 		if (!buf) {
 			BT_ERR("Unable to allocate URI buffer");
 			return -ENOBUFS;
diff --git a/nimble/host/mesh/src/cfg_cli.c b/nimble/host/mesh/src/cfg_cli.c
index 7a7928a3..ac8be55a 100644
--- a/nimble/host/mesh/src/cfg_cli.c
+++ b/nimble/host/mesh/src/cfg_cli.c
@@ -16,6 +16,7 @@
 #include <errno.h>
 #include <stdbool.h>
 
+#include "net.h"
 #include "foundation.h"
 
 #define CID_NVAL 0xffff
diff --git a/nimble/host/mesh/src/cfg_srv.c b/nimble/host/mesh/src/cfg_srv.c
index 0ce3af84..94fccb33 100644
--- a/nimble/host/mesh/src/cfg_srv.c
+++ b/nimble/host/mesh/src/cfg_srv.c
@@ -28,6 +28,7 @@
 #include "foundation.h"
 #include "friend.h"
 #include "testing.h"
+#include "settings.h"
 
 #define DEFAULT_TTL 7
 
@@ -57,7 +58,7 @@ static void hb_send(struct bt_mesh_model *model)
 	struct bt_mesh_net_tx tx = {
 		.sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx),
 		.ctx = &ctx,
-		.src = model->elem->addr,
+		.src = bt_mesh_model_elem(model)->addr,
 		.xmit = bt_mesh_net_transmit_get(),
 	};
 
@@ -241,7 +242,7 @@ static bool app_key_is_valid(u16_t app_idx)
 
 static u8_t _mod_pub_set(struct bt_mesh_model *model, u16_t pub_addr,
 			 u16_t app_idx, u8_t cred_flag, u8_t ttl, u8_t period,
-			 u8_t retransmit)
+			 u8_t retransmit, bool store)
 {
 	if (!model->pub) {
 		return STATUS_NVAL_PUB_PARAM;
@@ -256,6 +257,10 @@ static u8_t _mod_pub_set(struct bt_mesh_model *model, u16_t pub_addr,
 	}
 
 	if (pub_addr == BT_MESH_ADDR_UNASSIGNED) {
+		if (model->pub->addr == BT_MESH_ADDR_UNASSIGNED) {
+			return STATUS_SUCCESS;
+		}
+
 		model->pub->addr = BT_MESH_ADDR_UNASSIGNED;
 		model->pub->key = 0;
 		model->pub->cred = 0;
@@ -268,6 +273,10 @@ static u8_t _mod_pub_set(struct bt_mesh_model *model, u16_t pub_addr,
 			k_delayed_work_cancel(&model->pub->timer);
 		}
 
+		if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) {
+			bt_mesh_store_mod_pub(model);
+		}
+
 		return STATUS_SUCCESS;
 	}
 
@@ -295,6 +304,10 @@ static u8_t _mod_pub_set(struct bt_mesh_model *model, u16_t pub_addr,
 		}
 	}
 
+	if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) {
+		bt_mesh_store_mod_pub(model);
+	}
+
 	return STATUS_SUCCESS;
 }
 
@@ -302,7 +315,7 @@ u8_t mod_bind(struct bt_mesh_model *model, u16_t key_idx)
 {
 	int i;
 
-	BT_DBG("key_idx 0x%04x", key_idx);
+	BT_DBG("model %p key_idx 0x%03x", model, key_idx);
 
 	if (!app_key_is_valid(key_idx)) {
 		return STATUS_INVALID_APPKEY;
@@ -318,6 +331,11 @@ u8_t mod_bind(struct bt_mesh_model *model, u16_t key_idx)
 	for (i = 0; i < ARRAY_SIZE(model->keys); i++) {
 		if (model->keys[i] == BT_MESH_KEY_UNUSED) {
 			model->keys[i] = key_idx;
+
+			if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+				bt_mesh_store_mod_bind(model);
+			}
+
 			return STATUS_SUCCESS;
 		}
 	}
@@ -325,11 +343,11 @@ u8_t mod_bind(struct bt_mesh_model *model, u16_t key_idx)
 	return STATUS_INSUFF_RESOURCES;
 }
 
-u8_t mod_unbind(struct bt_mesh_model *model, u16_t key_idx)
+u8_t mod_unbind(struct bt_mesh_model *model, u16_t key_idx, bool store)
 {
 	int i;
 
-	BT_DBG("model %p key_idx 0x%04x", model, key_idx);
+	BT_DBG("model %p key_idx 0x%03x store %u", model, key_idx, store);
 
 	if (!app_key_is_valid(key_idx)) {
 		return STATUS_INVALID_APPKEY;
@@ -342,16 +360,20 @@ u8_t mod_unbind(struct bt_mesh_model *model, u16_t key_idx)
 
 		model->keys[i] = BT_MESH_KEY_UNUSED;
 
+		if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) {
+			bt_mesh_store_mod_bind(model);
+		}
+
 		if (model->pub && model->pub->key == key_idx) {
 			_mod_pub_set(model, BT_MESH_ADDR_UNASSIGNED,
-				     0, 0, 0, 0, 0);
+				     0, 0, 0, 0, 0, store);
 		}
 	}
 
 	return STATUS_SUCCESS;
 }
 
-static struct bt_mesh_app_key *app_key_alloc(u16_t app_idx)
+struct bt_mesh_app_key *bt_mesh_app_key_alloc(u16_t app_idx)
 {
 	int i;
 
@@ -425,7 +447,7 @@ static u8_t app_key_set(u16_t net_idx, u16_t app_idx, const u8_t val[16],
 			}
 		}
 
-		key = app_key_alloc(app_idx);
+		key = bt_mesh_app_key_alloc(app_idx);
 		if (!key) {
 			return STATUS_INSUFF_RESOURCES;
 		}
@@ -447,6 +469,11 @@ static u8_t app_key_set(u16_t net_idx, u16_t app_idx, const u8_t val[16],
 	key->app_idx = app_idx;
 	memcpy(keys->val, val, 16);
 
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		BT_DBG("Storing AppKey persistently");
+		bt_mesh_store_app_key(key);
+	}
+
 	return STATUS_SUCCESS;
 }
 
@@ -504,17 +531,30 @@ static void app_key_update(struct bt_mesh_model *model,
 	os_mbuf_free_chain(msg);
 }
 
+struct unbind_data {
+	u16_t app_idx;
+	bool store;
+};
+
 static void _mod_unbind(struct bt_mesh_model *mod, struct bt_mesh_elem *elem,
 			bool vnd, bool primary, void *user_data)
 {
-	u16_t *key_idx = user_data;
+	struct unbind_data *data = user_data;
 
-	mod_unbind(mod, *key_idx);
+	mod_unbind(mod, data->app_idx, data->store);
 }
 
-static void _app_key_del(struct bt_mesh_app_key *key)
+void bt_mesh_app_key_del(struct bt_mesh_app_key *key, bool store)
 {
-	bt_mesh_model_foreach(_mod_unbind, &key->app_idx);
+	struct unbind_data data = { .app_idx = key->app_idx, .store = store };
+
+	BT_DBG("AppIdx 0x%03x store %u", key->app_idx, store);
+
+	bt_mesh_model_foreach(_mod_unbind, &data);
+
+	if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) {
+		bt_mesh_clear_app_key(key);
+	}
 
 	key->net_idx = BT_MESH_KEY_UNUSED;
 	memset(key->keys, 0, sizeof(key->keys));
@@ -552,7 +592,7 @@ static void app_key_del(struct bt_mesh_model *model,
 		goto send_status;
 	}
 
-	_app_key_del(key);
+	bt_mesh_app_key_del(key, true);
 	status = STATUS_SUCCESS;
 
 send_status:
@@ -673,6 +713,10 @@ static void beacon_set(struct bt_mesh_model *model,
 		if (buf->om_data[0] != cfg->beacon) {
 			cfg->beacon = buf->om_data[0];
 
+			if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+				bt_mesh_store_cfg();
+			}
+
 			if (cfg->beacon) {
 				bt_mesh_beacon_enable();
 			} else {
@@ -732,7 +776,13 @@ static void default_ttl_set(struct bt_mesh_model *model,
 	if (!cfg) {
 		BT_WARN("No Configuration Server context available");
 	} else if (buf->om_data[0] <= BT_MESH_TTL_MAX && buf->om_data[0] != 0x01) {
-		cfg->default_ttl = buf->om_data[0];
+		if (cfg->default_ttl != buf->om_data[0]) {
+			cfg->default_ttl = buf->om_data[0];
+
+			if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+				bt_mesh_store_cfg();
+			}
+		}
 	} else {
 		BT_WARN("Prohibited Default TTL value 0x%02x", buf->om_data[0]);
 		goto done;
@@ -810,6 +860,11 @@ static void gatt_proxy_set(struct bt_mesh_model *model,
 	}
 
 	cfg->gatt_proxy = buf->om_data[0];
+
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		bt_mesh_store_cfg();
+	}
+
 	if (cfg->gatt_proxy == BT_MESH_GATT_PROXY_DISABLED) {
 		int i;
 
@@ -885,6 +940,10 @@ static void net_transmit_set(struct bt_mesh_model *model,
 		BT_WARN("No Configuration Server context available");
 	} else {
 		cfg->net_transmit = buf->om_data[0];
+
+		if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+			bt_mesh_store_cfg();
+		}
 	}
 
 	bt_mesh_model_msg_init(msg, OP_NET_TRANSMIT_STATUS);
@@ -944,6 +1003,10 @@ static void relay_set(struct bt_mesh_model *model,
 			change = (cfg->relay != buf->om_data[0]);
 			cfg->relay = buf->om_data[0];
 			cfg->relay_retransmit = buf->om_data[1];
+
+			if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+				bt_mesh_store_cfg();
+			}
 		}
 
 		BT_DBG("Relay 0x%02x (%s) xmit 0x%02x (count %u interval %u)",
@@ -1026,6 +1089,11 @@ static void mod_pub_get(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	mod_id = buf->om_data;
 
 	BT_DBG("elem_addr 0x%04x", elem_addr);
@@ -1069,6 +1137,11 @@ static void mod_pub_set(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	pub_addr = net_buf_simple_pull_le16(buf);
 	pub_app_idx = net_buf_simple_pull_le16(buf);
 	cred_flag = ((pub_app_idx >> 12) & BIT_MASK(1));
@@ -1107,7 +1180,7 @@ static void mod_pub_set(struct bt_mesh_model *model,
 	}
 
 	status = _mod_pub_set(mod, pub_addr, pub_app_idx, cred_flag, pub_ttl,
-			      pub_period, retransmit);
+			      pub_period, retransmit, true);
 
 send_status:
 	send_mod_pub_status(model, ctx, elem_addr, pub_addr, vnd, mod,
@@ -1207,6 +1280,11 @@ static void mod_pub_va_set(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	label_uuid = buf->om_data;
 	net_buf_simple_pull(buf, 16);
 
@@ -1250,7 +1328,7 @@ static void mod_pub_va_set(struct bt_mesh_model *model,
 	status = va_add(label_uuid, &pub_addr);
 	if (status == STATUS_SUCCESS) {
 		status = _mod_pub_set(mod, pub_addr, pub_app_idx, cred_flag,
-				      pub_ttl, pub_period, retransmit);
+				      pub_ttl, pub_period, retransmit, true);
 	}
 
 send_status:
@@ -1275,6 +1353,11 @@ static void mod_pub_va_set(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	net_buf_simple_pull(buf, 16);
 	mod_id = net_buf_simple_pull(buf, 4);
 
@@ -1351,6 +1434,11 @@ static void mod_sub_add(struct bt_mesh_model *model,
 	int i;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	sub_addr = net_buf_simple_pull_le16(buf);
 
 	BT_DBG("elem_addr 0x%04x, sub_addr 0x%04x", elem_addr, sub_addr);
@@ -1394,7 +1482,11 @@ static void mod_sub_add(struct bt_mesh_model *model,
 	} else {
 		status = STATUS_SUCCESS;
 
-		if ((MYNEWT_VAL(BLE_MESH_LOW_POWER))) {
+		if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+			bt_mesh_store_mod_sub(mod);
+		}
+
+		if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) {
 			bt_mesh_lpn_group_add(sub_addr);
 		}
 	}
@@ -1417,6 +1509,11 @@ static void mod_sub_del(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	sub_addr = net_buf_simple_pull_le16(buf);
 
 	BT_DBG("elem_addr 0x%04x sub_addr 0x%04x", elem_addr, sub_addr);
@@ -1454,6 +1551,10 @@ static void mod_sub_del(struct bt_mesh_model *model,
 	match = bt_mesh_model_find_group(mod, sub_addr);
 	if (match) {
 		*match = BT_MESH_ADDR_UNASSIGNED;
+
+		if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+			bt_mesh_store_mod_sub(mod);
+		}
 	}
 
 send_status:
@@ -1473,6 +1574,11 @@ static void mod_sub_overwrite(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	sub_addr = net_buf_simple_pull_le16(buf);
 
 	BT_DBG("elem_addr 0x%04x sub_addr 0x%04x", elem_addr, sub_addr);
@@ -1508,7 +1614,11 @@ static void mod_sub_overwrite(struct bt_mesh_model *model,
 		mod->groups[0] = sub_addr;
 		status = STATUS_SUCCESS;
 
-		if ((MYNEWT_VAL(BLE_MESH_LOW_POWER))) {
+		if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+			bt_mesh_store_mod_sub(mod);
+		}
+
+		if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) {
 			bt_mesh_lpn_group_add(sub_addr);
 		}
 	} else {
@@ -1533,6 +1643,10 @@ static void mod_sub_del_all(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
 
 	BT_DBG("elem_addr 0x%04x", elem_addr);
 
@@ -1558,6 +1672,10 @@ static void mod_sub_del_all(struct bt_mesh_model *model,
 
 	mod_sub_list_clear(mod);
 
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		bt_mesh_store_mod_sub(mod);
+	}
+
 	status = STATUS_SUCCESS;
 
 send_status:
@@ -1578,6 +1696,11 @@ static void mod_sub_get(struct bt_mesh_model *model,
 	int i;
 
 	addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	id = net_buf_simple_pull_le16(buf);
 
 	BT_DBG("addr 0x%04x id 0x%04x", addr, id);
@@ -1633,6 +1756,11 @@ static void mod_sub_get_vnd(struct bt_mesh_model *model,
 	int i;
 
 	addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	company = net_buf_simple_pull_le16(buf);
 	id = net_buf_simple_pull_le16(buf);
 
@@ -1694,6 +1822,11 @@ static void mod_sub_va_add(struct bt_mesh_model *model,
 	int i;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	label_uuid = buf->om_data;
 	net_buf_simple_pull(buf, 16);
 
@@ -1741,6 +1874,10 @@ static void mod_sub_va_add(struct bt_mesh_model *model,
 			bt_mesh_lpn_group_add(sub_addr);
 		}
 
+		if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+			bt_mesh_store_mod_sub(mod);
+		}
+
 		status = STATUS_SUCCESS;
 	}
 
@@ -1763,6 +1900,11 @@ static void mod_sub_va_del(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	label_uuid = buf->om_data;
 	net_buf_simple_pull(buf, 16);
 
@@ -1798,6 +1940,11 @@ static void mod_sub_va_del(struct bt_mesh_model *model,
 	match = bt_mesh_model_find_group(mod, sub_addr);
 	if (match) {
 		*match = BT_MESH_ADDR_UNASSIGNED;
+
+		if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+			bt_mesh_store_mod_sub(mod);
+		}
+
 		status = STATUS_SUCCESS;
 	} else {
 		status = STATUS_CANNOT_REMOVE;
@@ -1821,6 +1968,11 @@ static void mod_sub_va_overwrite(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	label_uuid = buf->om_data;
 	net_buf_simple_pull(buf, 16);
 	mod_id = buf->om_data;
@@ -1853,7 +2005,11 @@ static void mod_sub_va_overwrite(struct bt_mesh_model *model,
 		if (status == STATUS_SUCCESS) {
 			mod->groups[0] = sub_addr;
 
-			if ((MYNEWT_VAL(BLE_MESH_LOW_POWER))) {
+			if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+				bt_mesh_store_mod_sub(mod);
+			}
+
+			if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) {
 				bt_mesh_lpn_group_add(sub_addr);
 			}
 		}
@@ -1878,6 +2034,11 @@ static void mod_sub_va_add(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	net_buf_simple_pull(buf, 16);
 
 	mod_id = buf->om_data;
@@ -1914,6 +2075,11 @@ static void mod_sub_va_del(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	net_buf_simple_pull(buf, 16);
 
 	mod_id = buf->om_data;
@@ -1948,6 +2114,11 @@ static void mod_sub_va_overwrite(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	net_buf_simple_pull(buf, 18);
 
 	mod_id = buf->om_data;
@@ -2011,7 +2182,7 @@ static void net_key_add(struct bt_mesh_model *model,
 	if (!sub) {
 		int i;
 
-		for (sub = NULL, i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
+		for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
 			if (bt_mesh.sub[i].net_idx == BT_MESH_KEY_UNUSED) {
 				sub = &bt_mesh.sub[i];
 				break;
@@ -2047,6 +2218,11 @@ static void net_key_add(struct bt_mesh_model *model,
 
 	sub->net_idx = idx;
 
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		BT_DBG("Storing NetKey persistently");
+		bt_mesh_store_subnet(sub);
+	}
+
 	/* Make sure we have valid beacon data to be sent */
 	bt_mesh_net_beacon_update(sub);
 
@@ -2120,6 +2296,11 @@ static void net_key_update(struct bt_mesh_model *model,
 
 	sub->kr_phase = BT_MESH_KR_PHASE_1;
 
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		BT_DBG("Storing NetKey persistently");
+		bt_mesh_store_subnet(sub);
+	}
+
 	bt_mesh_net_beacon_update(sub);
 
 	send_net_key_status(model, ctx, idx, STATUS_SUCCESS);
@@ -2141,9 +2322,8 @@ static void net_key_del(struct bt_mesh_model *model,
 			struct bt_mesh_msg_ctx *ctx,
 			struct os_mbuf *buf)
 {
-	struct bt_mesh_cfg_srv *cfg = model->user_data;
 	struct bt_mesh_subnet *sub;
-	u16_t del_idx, i;
+	u16_t del_idx;
 	u8_t status;
 
 	del_idx = net_buf_simple_pull_le16(buf);
@@ -2171,26 +2351,7 @@ static void net_key_del(struct bt_mesh_model *model,
 		goto send_status;
 	}
 
-	if (cfg->hb_pub.net_idx == del_idx) {
-		hb_pub_disable(cfg);
-	}
-
-	/* Delete any app keys bound to this NetKey index */
-	for (i = 0; i < ARRAY_SIZE(bt_mesh.app_keys); i++) {
-		struct bt_mesh_app_key *key = &bt_mesh.app_keys[i];
-
-		if (key->net_idx == del_idx) {
-			_app_key_del(key);
-		}
-	}
-
-	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
-		bt_mesh_friend_clear_net_idx(del_idx);
-	}
-
-	memset(sub, 0, sizeof(*sub));
-	sub->net_idx = BT_MESH_KEY_UNUSED;
-
+	bt_mesh_subnet_del(sub, true);
 	status = STATUS_SUCCESS;
 
 send_status:
@@ -2371,6 +2532,11 @@ static void mod_app_bind(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	key_app_idx = net_buf_simple_pull_le16(buf);
 	mod_id = buf->om_data;
 
@@ -2426,6 +2592,11 @@ static void mod_app_unbind(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	key_app_idx = net_buf_simple_pull_le16(buf);
 	mod_id = buf->om_data;
 
@@ -2443,7 +2614,7 @@ static void mod_app_unbind(struct bt_mesh_model *model,
 		goto send_status;
 	}
 
-	status = mod_unbind(mod, key_app_idx);
+	status = mod_unbind(mod, key_app_idx, true);
 
 	if (IS_ENABLED(CONFIG_BT_TESTING) && status == STATUS_SUCCESS) {
 		bt_test_mesh_model_unbound(ctx->addr, mod, key_app_idx);
@@ -2475,6 +2646,11 @@ static void mod_app_get(struct bt_mesh_model *model,
 	bool vnd;
 
 	elem_addr = net_buf_simple_pull_le16(buf);
+	if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) {
+		BT_WARN("Prohibited element address");
+		return;
+	}
+
 	mod_id = buf->om_data;
 
 	BT_DBG("elem_addr 0x%04x", elem_addr);
@@ -2610,6 +2786,10 @@ static void friend_set(struct bt_mesh_model *model,
 	if (MYNEWT_VAL(BLE_MESH_FRIEND)) {
 		cfg->frnd = buf->om_data[0];
 
+		if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+			bt_mesh_store_cfg();
+		}
+
 		if (cfg->frnd == BT_MESH_FRIEND_DISABLED) {
 			bt_mesh_friend_clear_net_idx(BT_MESH_KEY_ANY);
 		}
@@ -2648,7 +2828,7 @@ static void lpn_timeout_get(struct bt_mesh_model *model,
 	bt_mesh_model_msg_init(msg, OP_LPN_TIMEOUT_STATUS);
 	net_buf_simple_add_le16(msg, lpn_addr);
 
-	if (!IS_ENABLED(CONFIG_BLUETOOTH_MESH_FRIEND)) {
+	if (!IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
 		timeout = 0;
 		goto send_rsp;
 	}
@@ -2929,6 +3109,10 @@ static void heartbeat_pub_set(struct bt_mesh_model *model,
 		}
 	}
 
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		bt_mesh_store_hb_pub();
+	}
+
 	hb_pub_send_status(model, ctx, STATUS_SUCCESS, NULL);
 
 	return;
@@ -3191,6 +3375,7 @@ int bt_mesh_cfg_srv_init(struct bt_mesh_model *model, bool primary)
 
 	k_delayed_work_init(&cfg->hb_pub.timer, hb_publish);
 	k_delayed_work_add_arg(&cfg->hb_pub.timer, cfg);
+	cfg->hb_pub.net_idx = BT_MESH_KEY_UNUSED;
 	cfg->hb_sub.expiry = 0;
 
 	cfg->model = model;
@@ -3200,6 +3385,22 @@ int bt_mesh_cfg_srv_init(struct bt_mesh_model *model, bool primary)
 	return 0;
 }
 
+static void mod_reset(struct bt_mesh_model *mod, struct bt_mesh_elem *elem,
+		      bool vnd, bool primary, void *user_data)
+{
+	/* Clear model state that isn't otherwise cleared. E.g. AppKey
+	 * binding and model publication is cleared as a consequence
+	 * of removing all app keys, however model subscription clearing
+	 * must be taken care of here.
+	 */
+
+	mod_sub_list_clear(mod);
+
+	if (IS_ENABLED(BT_SETTINGS)) {
+		bt_mesh_store_mod_sub(mod);
+	}
+}
+
 void bt_mesh_cfg_reset(void)
 {
 	struct bt_mesh_cfg_srv *cfg = conf;
@@ -3215,24 +3416,19 @@ void bt_mesh_cfg_reset(void)
 	cfg->hb_sub.dst = BT_MESH_ADDR_UNASSIGNED;
 	cfg->hb_sub.expiry = 0;
 
-	hb_pub_disable(cfg);
-
-	/* Delete all app keys */
-	for (i = 0; i < ARRAY_SIZE(bt_mesh.app_keys); i++) {
-		struct bt_mesh_app_key *key = &bt_mesh.app_keys[i];
-
-		if (key->net_idx != BT_MESH_KEY_UNUSED) {
-			_app_key_del(key);
-		}
-	}
-
+	/* Delete all net keys, which also takes care of all app keys which
+	 * are associated with each net key.
+	 */
 	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
 		struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
 
-		memset(sub, 0, sizeof(*sub));
-		sub->net_idx = BT_MESH_KEY_UNUSED;
+		if (sub->net_idx != BT_MESH_KEY_UNUSED) {
+			bt_mesh_subnet_del(sub, true);
+		}
 	}
 
+	bt_mesh_model_foreach(mod_reset, NULL);
+
 	memset(labels, 0, sizeof(labels));
 }
 
@@ -3354,3 +3550,52 @@ u8_t *bt_mesh_label_uuid_get(u16_t addr)
 
 	return NULL;
 }
+
+struct bt_mesh_hb_pub *bt_mesh_hb_pub_get(void)
+{
+	if (!conf) {
+		return NULL;
+	}
+
+	return &conf->hb_pub;
+}
+
+struct bt_mesh_cfg_srv *bt_mesh_cfg_get(void)
+{
+	return conf;
+}
+
+void bt_mesh_subnet_del(struct bt_mesh_subnet *sub, bool store)
+{
+	int i;
+
+	BT_DBG("NetIdx 0x%03x store %u", sub->net_idx, store);
+
+	if (conf && conf->hb_pub.net_idx == sub->net_idx) {
+		hb_pub_disable(conf);
+
+		if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) {
+			bt_mesh_store_hb_pub();
+		}
+	}
+
+	/* Delete any app keys bound to this NetKey index */
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.app_keys); i++) {
+		struct bt_mesh_app_key *key = &bt_mesh.app_keys[i];
+
+		if (key->net_idx == sub->net_idx) {
+			bt_mesh_app_key_del(key, store);
+		}
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
+		bt_mesh_friend_clear_net_idx(sub->net_idx);
+	}
+
+	if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) {
+		bt_mesh_clear_subnet(sub);
+	}
+
+	memset(sub, 0, sizeof(*sub));
+	sub->net_idx = BT_MESH_KEY_UNUSED;
+}
diff --git a/nimble/host/mesh/src/foundation.h b/nimble/host/mesh/src/foundation.h
index 14e4c72a..cd92c023 100644
--- a/nimble/host/mesh/src/foundation.h
+++ b/nimble/host/mesh/src/foundation.h
@@ -129,6 +129,9 @@ void bt_mesh_attention(struct bt_mesh_model *model, u8_t time);
 
 u8_t *bt_mesh_label_uuid_get(u16_t addr);
 
+struct bt_mesh_hb_pub *bt_mesh_hb_pub_get(void);
+struct bt_mesh_cfg_srv *bt_mesh_cfg_get(void);
+
 u8_t bt_mesh_net_transmit_get(void);
 u8_t bt_mesh_relay_get(void);
 u8_t bt_mesh_friend_get(void);
@@ -137,6 +140,11 @@ u8_t bt_mesh_beacon_get(void);
 u8_t bt_mesh_gatt_proxy_get(void);
 u8_t bt_mesh_default_ttl_get(void);
 
+void bt_mesh_subnet_del(struct bt_mesh_subnet *sub, bool store);
+
+struct bt_mesh_app_key *bt_mesh_app_key_alloc(u16_t app_idx);
+void bt_mesh_app_key_del(struct bt_mesh_app_key *key, bool store);
+
 static inline void key_idx_pack(struct os_mbuf *buf,
 				u16_t idx1, u16_t idx2)
 {
diff --git a/nimble/host/mesh/src/friend.c b/nimble/host/mesh/src/friend.c
index b2cd4ed7..cca4363d 100644
--- a/nimble/host/mesh/src/friend.c
+++ b/nimble/host/mesh/src/friend.c
@@ -47,8 +47,7 @@ static struct os_mempool friend_buf_mempool;
 /* PDUs from Friend to the LPN should only be transmitted once with the
  * smallest possible interval (20ms).
  */
-#define FRIEND_XMIT_COUNT   0
-#define FRIEND_XMIT_INT     20
+#define FRIEND_XMIT         BT_MESH_TRANSMIT(0, 20)
 
 struct friend_pdu_info {
 	u16_t  src;
@@ -98,8 +97,7 @@ static struct os_mbuf *friend_buf_alloc(u16_t src)
 	do {
 		buf = bt_mesh_adv_create_from_pool(&friend_os_mbuf_pool, adv_alloc,
 						   BT_MESH_ADV_DATA,
-						   FRIEND_XMIT_COUNT,
-						   FRIEND_XMIT_INT, K_NO_WAIT);
+						   FRIEND_XMIT, K_NO_WAIT);
 		if (!buf) {
 			discard_buffer();
 		}
@@ -383,6 +381,7 @@ static struct os_mbuf *encode_friend_ctl(struct bt_mesh_friend *frnd,
 					 struct os_mbuf *sdu)
 {
 	struct friend_pdu_info info;
+	u32_t seq;
 
 	BT_DBG("LPN 0x%04x", frnd->lpn);
 
@@ -394,9 +393,10 @@ static struct os_mbuf *encode_friend_ctl(struct bt_mesh_friend *frnd,
 	info.ctl = 1;
 	info.ttl = 0;
 
-	info.seq[0] = (bt_mesh.seq >> 16);
-	info.seq[1] = (bt_mesh.seq >> 8);
-	info.seq[2] = bt_mesh.seq++;
+	seq = bt_mesh_next_seq();
+	info.seq[0] = seq >> 16;
+	info.seq[1] = seq >> 8;
+	info.seq[2] = seq;
 
 	info.iv_index = BT_MESH_NET_IVI_TX;
 
@@ -1120,7 +1120,7 @@ static void friend_lpn_enqueue_rx(struct bt_mesh_friend *frnd,
 	}
 
 	info.src = rx->ctx.addr;
-	info.dst = rx->dst;
+	info.dst = rx->ctx.recv_dst;
 
 	if (rx->net_if == BT_MESH_NET_IF_LOCAL) {
 		info.ttl = rx->ctx.recv_ttl;
@@ -1159,6 +1159,7 @@ static void friend_lpn_enqueue_tx(struct bt_mesh_friend *frnd,
 {
 	struct friend_pdu_info info;
 	struct os_mbuf *buf;
+	u32_t seq;
 
 	BT_DBG("LPN 0x%04x", frnd->lpn);
 
@@ -1172,9 +1173,10 @@ static void friend_lpn_enqueue_tx(struct bt_mesh_friend *frnd,
 	info.ttl = tx->ctx->send_ttl;
 	info.ctl = (tx->ctx->app_idx == BT_MESH_KEY_UNUSED);
 
-	info.seq[0] = (bt_mesh.seq >> 16);
-	info.seq[1] = (bt_mesh.seq >> 8);
-	info.seq[2] = bt_mesh.seq++;
+	seq = bt_mesh_next_seq();
+	info.seq[0] = seq >> 16;
+	info.seq[1] = seq >> 8;
+	info.seq[2] = seq;
 
 	info.iv_index = BT_MESH_NET_IVI_TX;
 
@@ -1255,12 +1257,14 @@ void bt_mesh_friend_enqueue_rx(struct bt_mesh_net_rx *rx,
 	}
 
 	BT_DBG("recv_ttl %u net_idx 0x%04x src 0x%04x dst 0x%04x",
-	       rx->ctx.recv_ttl, rx->sub->net_idx, rx->ctx.addr, rx->dst);
+	       rx->ctx.recv_ttl, rx->sub->net_idx, rx->ctx.addr,
+	       rx->ctx.recv_dst);
 
 	for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
 		struct bt_mesh_friend *frnd = &bt_mesh.frnd[i];
 
-		if (friend_lpn_matches(frnd, rx->sub->net_idx, rx->dst)) {
+		if (friend_lpn_matches(frnd, rx->sub->net_idx,
+				       rx->ctx.recv_dst)) {
 			friend_lpn_enqueue_rx(frnd, rx, type, seq_auth, sbuf);
 		}
 	}
diff --git a/nimble/host/mesh/src/glue.c b/nimble/host/mesh/src/glue.c
index f791df78..adee8662 100644
--- a/nimble/host/mesh/src/glue.c
+++ b/nimble/host/mesh/src/glue.c
@@ -17,6 +17,7 @@
  * under the License.
  */
 
+#include "base64/base64.h"
 #include "mesh/glue.h"
 #include "adv.h"
 #ifndef MYNEWT
@@ -825,3 +826,21 @@ void net_buf_slist_merge_slist(struct net_buf_slist_t *list,
 
 	STAILQ_INIT(list);
 }
+
+int settings_bytes_from_str(char *val_str, void *vp, int *len)
+{
+    *len = base64_decode(val_str, vp);
+    return 0;
+}
+
+char *settings_str_from_bytes(void *vp, int vp_len, char *buf, int buf_len)
+{
+    if (BASE64_ENCODE_SIZE(vp_len) > buf_len) {
+        return NULL;
+    }
+
+    base64_encode(vp, vp_len, buf, 1);
+
+    return buf;
+}
+
diff --git a/nimble/host/mesh/src/health_cli.c b/nimble/host/mesh/src/health_cli.c
index b480daa4..68543415 100644
--- a/nimble/host/mesh/src/health_cli.c
+++ b/nimble/host/mesh/src/health_cli.c
@@ -19,7 +19,6 @@
 #include "adv.h"
 #include "net.h"
 #include "transport.h"
-#include "access.h"
 #include "foundation.h"
 #include "mesh/health_cli.h"
 
diff --git a/nimble/host/mesh/src/mesh.c b/nimble/host/mesh/src/mesh.c
index 85db989f..ac8bd5f7 100644
--- a/nimble/host/mesh/src/mesh.c
+++ b/nimble/host/mesh/src/mesh.c
@@ -28,13 +28,13 @@
 #include "proxy.h"
 #include "shell.h"
 #include "mesh_priv.h"
+#include "settings.h"
 
 u8_t g_mesh_addr_type;
-static bool provisioned;
 
 int bt_mesh_provision(const u8_t net_key[16], u16_t net_idx,
-		      u8_t flags, u32_t iv_index, u32_t seq,
-		      u16_t addr, const u8_t dev_key[16])
+		      u8_t flags, u32_t iv_index, u16_t addr,
+		      const u8_t dev_key[16])
 {
 	int err;
 
@@ -55,60 +55,39 @@ int bt_mesh_provision(const u8_t net_key[16], u16_t net_idx,
 		return err;
 	}
 
-	bt_mesh.seq = seq;
+	bt_mesh.seq = 0;
 
 	bt_mesh_comp_provision(addr);
 
 	memcpy(bt_mesh.dev_key, dev_key, 16);
 
-	provisioned = true;
-
-	if (bt_mesh_beacon_get() == BT_MESH_BEACON_ENABLED) {
-		bt_mesh_beacon_enable();
-	} else {
-		bt_mesh_beacon_disable();
-	}
-
-	if (MYNEWT_VAL(BLE_MESH_GATT_PROXY) &&
-	    bt_mesh_gatt_proxy_get() != BT_MESH_GATT_PROXY_NOT_SUPPORTED) {
-		bt_mesh_proxy_gatt_enable();
-		bt_mesh_adv_update();
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		BT_DBG("Storing network information persistently");
+		bt_mesh_store_net();
+		bt_mesh_store_subnet(&bt_mesh.sub[0]);
+		bt_mesh_store_iv(false);
 	}
 
-	if ((MYNEWT_VAL(BLE_MESH_LOW_POWER))) {
-		bt_mesh_lpn_init();
-	} else {
-		bt_mesh_scan_enable();
-	}
-
-	if ((MYNEWT_VAL(BLE_MESH_FRIEND))) {
-		bt_mesh_friend_init();
-	}
-
-	if (MYNEWT_VAL(BLE_MESH_PROV)) {
-		bt_mesh_prov_complete(net_idx, addr);
-	}
+	bt_mesh_net_start();
 
 	return 0;
 }
 
 void bt_mesh_reset(void)
 {
-	if (!provisioned) {
+	if (!bt_mesh.valid) {
 		return;
 	}
 
-	bt_mesh_comp_unprovision();
-
 	bt_mesh.iv_index = 0;
 	bt_mesh.seq = 0;
 	bt_mesh.iv_update = 0;
 	bt_mesh.pending_update = 0;
 	bt_mesh.valid = 0;
-	bt_mesh.last_update = 0;
+	bt_mesh.ivu_duration = 0;
 	bt_mesh.ivu_initiator = 0;
 
-	k_delayed_work_cancel(&bt_mesh.ivu_complete);
+	k_delayed_work_cancel(&bt_mesh.ivu_timer);
 
 	bt_mesh_cfg_reset();
 
@@ -131,15 +110,17 @@ void bt_mesh_reset(void)
 		bt_mesh_proxy_prov_enable();
 	}
 
-	memset(bt_mesh.dev_key, 0, sizeof(bt_mesh.dev_key));
-
-	memset(bt_mesh.rpl, 0, sizeof(bt_mesh.rpl));
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		bt_mesh_clear_net();
+	}
 
-	provisioned = false;
+	memset(bt_mesh.dev_key, 0, sizeof(bt_mesh.dev_key));
 
 	bt_mesh_scan_disable();
 	bt_mesh_beacon_disable();
 
+	bt_mesh_comp_unprovision();
+
 	if (IS_ENABLED(CONFIG_BT_MESH_PROV)) {
 		bt_mesh_prov_reset();
 	}
@@ -147,7 +128,7 @@ void bt_mesh_reset(void)
 
 bool bt_mesh_is_provisioned(void)
 {
-	return provisioned;
+	return bt_mesh.valid;
 }
 
 int bt_mesh_prov_enable(bt_mesh_prov_bearer_t bearers)
@@ -255,5 +236,9 @@ int bt_mesh_init(uint8_t own_addr_type, const struct bt_mesh_prov *prov,
 
 	ble_gap_mesh_cb_register(bt_mesh_gap_event, NULL);
 
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		bt_mesh_settings_init();
+	}
+
 	return 0;
 }
diff --git a/nimble/host/mesh/src/mesh_priv.h b/nimble/host/mesh/src/mesh_priv.h
index ab664f3a..0a26c903 100644
--- a/nimble/host/mesh/src/mesh_priv.h
+++ b/nimble/host/mesh/src/mesh_priv.h
@@ -15,6 +15,7 @@
 #define BT_MESH_ADDR_IS_GROUP(addr) ((addr) >= 0xc000 && (addr) <= 0xff00)
 #define BT_MESH_ADDR_IS_VIRTUAL(addr) ((addr) >= 0x8000 && (addr) < 0xc000)
 #define BT_MESH_ADDR_IS_RFU(addr) ((addr) >= 0xff00 && (addr) <= 0xfffb)
+struct bt_mesh_net;
 
 #define OP_GEN_ONOFF_GET		BT_MESH_MODEL_OP_2(0x82, 0x01)
 #define OP_GEN_ONOFF_SET		BT_MESH_MODEL_OP_2(0x82, 0x02)
diff --git a/nimble/host/mesh/src/net.c b/nimble/host/mesh/src/net.c
index c4034d35..75d3a1be 100644
--- a/nimble/host/mesh/src/net.c
+++ b/nimble/host/mesh/src/net.c
@@ -28,6 +28,8 @@
 #include "access.h"
 #include "foundation.h"
 #include "beacon.h"
+#include "settings.h"
+#include "prov.h"
 
 /* Minimum valid Mesh Network PDU length. The Network headers
  * themselves take up 9 bytes. After that there is a minumum of 1 byte
@@ -40,14 +42,6 @@
 /* Seq limit after IV Update is triggered */
 #define IV_UPDATE_SEQ_LIMIT 8000000
 
-#if MYNEWT_VAL(BLE_MESH_IV_UPDATE_TEST)
-/* Small test timeout for IV Update Procedure testing */
-#define IV_UPDATE_TIMEOUT  K_SECONDS(120)
-#else
-/* Maximum time to stay in IV Update mode (96 < time < 144) */
-#define IV_UPDATE_TIMEOUT  K_HOURS(120)
-#endif /* MYNEWT_VAL(BLE_MESH_IV_UPDATE_TEST) */
-
 #define IVI(pdu)           ((pdu)[0] >> 7)
 #define NID(pdu)           ((pdu)[0] & 0x7f)
 #define CTL(pdu)           ((pdu)[1] >> 7)
@@ -484,8 +478,11 @@ int bt_mesh_net_create(u16_t idx, u8_t flags, const u8_t key[16],
 	bt_mesh.iv_index = iv_index;
 	bt_mesh.iv_update = BT_MESH_IV_UPDATE(flags);
 
-	/* Set initial IV Update procedure state time-stamp */
-	bt_mesh.last_update = k_uptime_get();
+	/* Set minimum required hours, since the 96-hour minimum requirement
+	 * doesn't apply straight after provisioning (since we can't know how
+	 * long has actually passed since the network changed its state).
+	 */
+	bt_mesh.ivu_duration = BT_MESH_IVU_MIN_HOURS;
 
 	/* Make sure we have valid beacon data to be sent */
 	bt_mesh_net_beacon_update(sub);
@@ -581,6 +578,8 @@ void bt_mesh_rpl_reset(void)
 void bt_mesh_iv_update_test(bool enable)
 {
 	bt_mesh.ivu_test = enable;
+	/* Reset the duration variable - needed for some PTS tests */
+	bt_mesh.ivu_duration = 0;
 }
 
 bool bt_mesh_iv_update(void)
@@ -666,18 +665,10 @@ bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update)
 			BT_DBG("Already in Normal state");
 			return false;
 		}
-
-		if (iv_index != bt_mesh.iv_index + 1) {
-			BT_WARN("Wrong new IV Index: 0x%08x != 0x%08x + 1",
-				iv_index, bt_mesh.iv_index);
-			return false;
-		}
 	}
 
-	if (!MYNEWT_VAL(BLE_MESH_IV_UPDATE_TEST) || !bt_mesh.ivu_test) {
-		s64_t delta = k_uptime_get() - bt_mesh.last_update;
-
-		if (delta < K_HOURS(96)) {
+	if (!(IS_ENABLED(CONFIG_BT_MESH_IV_UPDATE_TEST) && bt_mesh.ivu_test)) {
+		if (bt_mesh.ivu_duration < BT_MESH_IVU_MIN_HOURS) {
 			BT_WARN("IV Update before minimum duration");
 			return false;
 		}
@@ -692,6 +683,7 @@ bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update)
 
 do_update:
 	bt_mesh.iv_update = iv_update;
+	bt_mesh.ivu_duration = 0;
 
 	if (bt_mesh.iv_update) {
 		bt_mesh.iv_index = iv_index;
@@ -699,17 +691,12 @@ bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update)
 		       bt_mesh.iv_index);
 
 		bt_mesh_rpl_reset();
-
-		k_delayed_work_submit(&bt_mesh.ivu_complete,
-				      IV_UPDATE_TIMEOUT);
 	} else {
 		BT_DBG("Normal mode entered");
 		bt_mesh.seq = 0;
-		k_delayed_work_cancel(&bt_mesh.ivu_complete);
 	}
 
-	/* Store time-stamp of the IV procedure state change */
-	bt_mesh.last_update = k_uptime_get();
+	k_delayed_work_submit(&bt_mesh.ivu_timer, BT_MESH_IVU_TIMEOUT);
 
 	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
 		if (bt_mesh.sub[i].net_idx != BT_MESH_KEY_UNUSED) {
@@ -717,14 +704,30 @@ bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update)
 		}
 	}
 
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		bt_mesh_store_iv(false);
+	}
+
 	return true;
 }
 
+u32_t bt_mesh_next_seq(void)
+{
+	u32_t seq = bt_mesh.seq++;
+
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		bt_mesh_store_seq();
+	}
+
+	return seq;
+}
+
 int bt_mesh_net_resend(struct bt_mesh_subnet *sub, struct os_mbuf *buf,
 		       bool new_key, const struct bt_mesh_send_cb *cb,
 		       void *cb_data)
 {
 	const u8_t *enc, *priv;
+	u32_t seq;
 	int err;
 
 	BT_DBG("net_idx 0x%04x new_key %u len %u", sub->net_idx, new_key,
@@ -745,10 +748,10 @@ int bt_mesh_net_resend(struct bt_mesh_subnet *sub, struct os_mbuf *buf,
 		return err;
 	}
 
-	/* Update with a new sequence number */
-	buf->om_data[2] = (bt_mesh.seq >> 16);
-	buf->om_data[3] = (bt_mesh.seq >> 8);
-	buf->om_data[4] = bt_mesh.seq++;
+	seq = bt_mesh_next_seq();
+	buf->om_data[2] = seq >> 16;
+	buf->om_data[3] = seq >> 8;
+	buf->om_data[4] = seq;
 
 	err = bt_mesh_net_encrypt(enc, buf, BT_MESH_NET_IVI_TX, false);
 	if (err) {
@@ -788,6 +791,7 @@ int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
 		       bool proxy)
 {
 	const bool ctl = (tx->ctx->app_idx == BT_MESH_KEY_UNUSED);
+	u32_t seq_val;
 	u8_t nid;
 	const u8_t *enc, *priv;
 	u8_t *seq;
@@ -808,9 +812,10 @@ int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
 	net_buf_simple_push_be16(buf, tx->src);
 
 	seq = net_buf_simple_push(buf, 3);
-	seq[0] = (bt_mesh.seq >> 16);
-	seq[1] = (bt_mesh.seq >> 8);
-	seq[2] = bt_mesh.seq++;
+	seq_val = bt_mesh_next_seq();
+	seq[0] = seq_val >> 16;
+	seq[1] = seq_val >> 8;
+	seq[2] = seq_val;
 
 	if (ctl) {
 		net_buf_simple_push_u8(buf, tx->ctx->send_ttl | 0x80);
@@ -1149,7 +1154,8 @@ static void bt_mesh_net_relay(struct os_mbuf *sbuf,
 		return;
 	}
 
-	BT_DBG("TTL %u CTL %u dst 0x%04x", rx->ctx.recv_ttl, rx->ctl, rx->dst);
+	BT_DBG("TTL %u CTL %u dst 0x%04x", rx->ctx.recv_ttl, rx->ctl,
+	       rx->ctx.recv_dst);
 
 	/* The Relay Retransmit state is only applied to adv-adv relaying.
 	 * Anything else (like GATT to adv, or locally originated packets)
@@ -1161,9 +1167,7 @@ static void bt_mesh_net_relay(struct os_mbuf *sbuf,
 		transmit = bt_mesh_net_transmit_get();
 	}
 
-	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA,
-				 BT_MESH_TRANSMIT_COUNT(transmit),
-				 BT_MESH_TRANSMIT_INT(transmit), K_NO_WAIT);
+	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, transmit, K_NO_WAIT);
 	if (!buf) {
 		BT_ERR("Out of relay buffers");
 		return;
@@ -1213,8 +1217,8 @@ static void bt_mesh_net_relay(struct os_mbuf *sbuf,
 	if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) &&
 	    (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED ||
 	     rx->net_if == BT_MESH_NET_IF_LOCAL)) {
-		if (bt_mesh_proxy_relay(buf, rx->dst) &&
-			    BT_MESH_ADDR_IS_UNICAST(rx->dst)) {
+		if (bt_mesh_proxy_relay(buf, rx->ctx.recv_dst) &&
+		    BT_MESH_ADDR_IS_UNICAST(rx->ctx.recv_dst)) {
 			goto done;
 		}
 	}
@@ -1265,18 +1269,18 @@ int bt_mesh_net_decode(struct os_mbuf *data, enum bt_mesh_net_if net_if,
 
 	rx->ctl = CTL(buf->om_data);
 	rx->seq = SEQ(buf->om_data);
-	rx->dst = DST(buf->om_data);
+	rx->ctx.recv_dst = DST(buf->om_data);
 
 	BT_DBG("Decryption successful. Payload len %u: %s", buf->om_len,
 		   bt_hex(buf->om_data, buf->om_len));
 
 	if (net_if != BT_MESH_NET_IF_PROXY_CFG &&
-	    rx->dst == BT_MESH_ADDR_UNASSIGNED) {
+	    rx->ctx.recv_dst == BT_MESH_ADDR_UNASSIGNED) {
 		BT_ERR("Destination address is unassigned; dropping packet");
 		return -EBADMSG;
 	}
 
-	if (BT_MESH_ADDR_IS_RFU(rx->dst)) {
+	if (BT_MESH_ADDR_IS_RFU(rx->ctx.recv_dst)) {
 		BT_ERR("Destination address is RFU; dropping packet");
 		return -EBADMSG;
 	}
@@ -1286,7 +1290,7 @@ int bt_mesh_net_decode(struct os_mbuf *data, enum bt_mesh_net_if net_if,
 		return -EBADMSG;
 	}
 
-	BT_DBG("src 0x%04x dst 0x%04x ttl %u", rx->ctx.addr, rx->dst,
+	BT_DBG("src 0x%04x dst 0x%04x ttl %u", rx->ctx.addr, rx->ctx.recv_dst,
 	       rx->ctx.recv_ttl);
 	BT_DBG("PDU: %s", bt_hex(buf->om_data, buf->om_len));
 
@@ -1319,15 +1323,15 @@ void bt_mesh_net_recv(struct os_mbuf *data, s8_t rssi,
 		bt_mesh_proxy_addr_add(data, rx.ctx.addr);
 	}
 
-	rx.local_match = (bt_mesh_fixed_group_match(rx.dst) ||
-			  bt_mesh_elem_find(rx.dst));
+	rx.local_match = (bt_mesh_fixed_group_match(rx.ctx.recv_dst) ||
+			  bt_mesh_elem_find(rx.ctx.recv_dst));
 
 	bt_mesh_trans_recv(buf, &rx);
 
 	/* Relay if this was a group/virtual address, or if the destination
 	 * was neither a local element nor an LPN we're Friends for.
 	 */
-	if (!BT_MESH_ADDR_IS_UNICAST(rx.dst) ||
+	if (!BT_MESH_ADDR_IS_UNICAST(rx.ctx.recv_dst) ||
 	    (!rx.local_match && !rx.friend_match)) {
 		net_buf_simple_restore(buf, &state);
 		bt_mesh_net_relay(buf, &rx);
@@ -1337,17 +1341,66 @@ void bt_mesh_net_recv(struct os_mbuf *data, s8_t rssi,
     os_mbuf_free_chain(buf);
 }
 
-static void ivu_complete(struct ble_npl_event *work)
+static void ivu_refresh(struct ble_npl_event *work)
 {
-	BT_DBG("");
+	bt_mesh.ivu_duration += BT_MESH_IVU_HOURS;
+
+	BT_DBG("%s for %u hour%s",
+	       bt_mesh.iv_update ? "IVU in Progress" : "IVU Normal mode",
+	       bt_mesh.ivu_duration, bt_mesh.ivu_duration == 1 ? "" : "s");
+
+	if (bt_mesh.ivu_duration < BT_MESH_IVU_MIN_HOURS) {
+		if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+			bt_mesh_store_iv(true);
+		}
+
+		k_delayed_work_submit(&bt_mesh.ivu_timer, BT_MESH_IVU_TIMEOUT);
+		return;
+	}
+
+	if (bt_mesh.iv_update) {
+		bt_mesh_beacon_ivu_initiator(true);
+		bt_mesh_net_iv_update(bt_mesh.iv_index, false);
+	} else if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		bt_mesh_store_iv(true);
+	}
+}
+
+void bt_mesh_net_start(void)
+{
+	if (bt_mesh_beacon_get() == BT_MESH_BEACON_ENABLED) {
+		bt_mesh_beacon_enable();
+	} else {
+		bt_mesh_beacon_disable();
+	}
 
-	bt_mesh_beacon_ivu_initiator(true);
-	bt_mesh_net_iv_update(bt_mesh.iv_index, false);
+	if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) &&
+	    bt_mesh_gatt_proxy_get() != BT_MESH_GATT_PROXY_NOT_SUPPORTED) {
+		bt_mesh_proxy_gatt_enable();
+		bt_mesh_adv_update();
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) {
+		bt_mesh_lpn_init();
+	} else {
+		bt_mesh_scan_enable();
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
+		bt_mesh_friend_init();
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_PROV)) {
+		u16_t net_idx = bt_mesh.sub[0].net_idx;
+		u16_t addr = bt_mesh_primary_addr();
+
+		bt_mesh_prov_complete(net_idx, addr);
+	}
 }
 
 void bt_mesh_net_init(void)
 {
-	k_delayed_work_init(&bt_mesh.ivu_complete, ivu_complete);
+	k_delayed_work_init(&bt_mesh.ivu_timer, ivu_refresh);
 
 	k_work_init(&bt_mesh.local_work, bt_mesh_net_local);
 	net_buf_slist_init(&bt_mesh.local_queue);
diff --git a/nimble/host/mesh/src/net.h b/nimble/host/mesh/src/net.h
index c08625eb..8b1557ab 100644
--- a/nimble/host/mesh/src/net.h
+++ b/nimble/host/mesh/src/net.h
@@ -25,6 +25,12 @@
 #include "mesh/mesh.h"
 #include "mesh/glue.h"
 
+/* How many hours in between updating IVU duration */
+#define BT_MESH_IVU_MIN_HOURS      96
+#define BT_MESH_IVU_HOURS          (BT_MESH_IVU_MIN_HOURS /     \
+				    CONFIG_BT_MESH_IVU_DIVIDER)
+#define BT_MESH_IVU_TIMEOUT        K_HOURS(BT_MESH_IVU_HOURS)
+
 struct bt_mesh_app_key {
 	u16_t net_idx;
 	u16_t app_idx;
@@ -72,6 +78,9 @@ struct bt_mesh_subnet {
 struct bt_mesh_rpl {
 	u16_t src;
 	bool  old_iv;
+#if defined(CONFIG_BT_SETTINGS)
+	bool  store;
+#endif
 	u32_t seq;
 };
 
@@ -189,6 +198,21 @@ struct bt_mesh_lpn {
 	ATOMIC_DEFINE(to_remove, LPN_GROUPS);
 };
 
+/* bt_mesh_net.flags, mainly used for pending storage actions */
+enum {
+	BT_MESH_RPL_PENDING,
+	BT_MESH_KEYS_PENDING,
+	BT_MESH_NET_PENDING,
+	BT_MESH_IV_PENDING,
+	BT_MESH_SEQ_PENDING,
+	BT_MESH_HB_PUB_PENDING,
+	BT_MESH_CFG_PENDING,
+	BT_MESH_MOD_PENDING,
+
+	/* Don't touch - intentionally last */
+	BT_MESH_FLAG_COUNT,
+};
+
 struct bt_mesh_net {
 	u32_t iv_index;          /* Current IV Index */
 	u32_t seq:24,            /* Next outgoing sequence number */
@@ -198,7 +222,7 @@ struct bt_mesh_net {
 	      pending_update:1,  /* Update blocked by SDU in progress */
 	      valid:1;           /* 0 if unused */
 
-	s64_t last_update;       /* Time since last IV Update change */
+	ATOMIC_DEFINE(flags, BT_MESH_FLAG_COUNT);
 
 	/* Local network interface */
 	struct ble_npl_callout local_work;
@@ -213,8 +237,11 @@ struct bt_mesh_net {
 	struct bt_mesh_lpn lpn;  /* Low Power Node state */
 #endif
 
-	/* Timer to transition IV Update in Progress state */
-	struct k_delayed_work ivu_complete;
+	/* Number of hours in current IV Update state */
+	u8_t  ivu_duration;
+
+	/* Timer to track duration in current IV Update state */
+	struct k_delayed_work ivu_timer;
 
 	u8_t dev_key[16];
 
@@ -238,7 +265,6 @@ struct bt_mesh_net_rx {
 	struct bt_mesh_subnet *sub;
 	struct bt_mesh_msg_ctx ctx;
 	u32_t  seq;            /* Sequence Number */
-	u16_t  dst;            /* Destination address */
 	u8_t   old_iv:1,       /* iv_index - 1 was used */
 	       new_key:1,      /* Data was encrypted with updated key */
 	       friend_cred:1,  /* Data was encrypted with friend cred */
@@ -309,6 +335,10 @@ int bt_mesh_net_decode(struct os_mbuf *data, enum bt_mesh_net_if net_if,
 void bt_mesh_net_recv(struct os_mbuf *data, s8_t rssi,
 		      enum bt_mesh_net_if net_if);
 
+u32_t bt_mesh_next_seq(void);
+
+void bt_mesh_net_start(void);
+
 void bt_mesh_net_init(void);
 
 /* Friendship Credential Management */
diff --git a/nimble/host/mesh/src/prov.c b/nimble/host/mesh/src/prov.c
index 10617924..5943f0d2 100644
--- a/nimble/host/mesh/src/prov.c
+++ b/nimble/host/mesh/src/prov.c
@@ -29,8 +29,7 @@
 #include "testing.h"
 
 /* 3 transmissions, 20ms interval */
-#define PROV_XMIT_COUNT        2
-#define PROV_XMIT_INT          20
+#define PROV_XMIT              BT_MESH_TRANSMIT(2, 20)
 
 #define AUTH_METHOD_NO_OOB     0x00
 #define AUTH_METHOD_STATIC     0x01
@@ -262,8 +261,7 @@ static struct os_mbuf *adv_buf_create(void)
 {
 	struct os_mbuf *buf;
 
-	buf = bt_mesh_adv_create(BT_MESH_ADV_PROV, PROV_XMIT_COUNT,
-				 PROV_XMIT_INT, BUF_TIMEOUT);
+	buf = bt_mesh_adv_create(BT_MESH_ADV_PROV, PROV_XMIT, BUF_TIMEOUT);
 	if (!buf) {
 		BT_ERR("Out of provisioning buffers");
 		assert(0);
@@ -1008,6 +1006,15 @@ static void prov_random(const u8_t *data)
     os_mbuf_free_chain(rnd);
 }
 
+static inline bool is_pb_gatt(void)
+{
+#if MYNEWT_VAL(BLE_MESH_PB_GATT)
+	return !!link.conn_handle;
+#else
+	return false;
+#endif
+}
+
 static void prov_data(const u8_t *data)
 {
 	struct os_mbuf *msg = PROV_BUF(1);
@@ -1020,6 +1027,7 @@ static void prov_data(const u8_t *data)
 	u16_t addr;
 	u16_t net_idx;
 	int err;
+	bool identity_enable;
 
 	BT_DBG("");
 
@@ -1071,14 +1079,21 @@ static void prov_data(const u8_t *data)
 	/* Ignore any further PDUs on this link */
 	link.expect = 0;
 
-	bt_mesh_provision(pdu, net_idx, flags, iv_index, 0, addr, dev_key);
+	/* Store info, since bt_mesh_provision() will end up clearing it */
+	if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) {
+		identity_enable = is_pb_gatt();
+	} else {
+		identity_enable = false;
+	}
+
+	bt_mesh_provision(pdu, net_idx, flags, iv_index, addr, dev_key);
 
-#if MYNEWT_VAL(BLE_MESH_PB_GATT) && MYNEWT_VAL(BLE_MESH_GATT_PROXY)
 	/* After PB-GATT provisioning we should start advertising
 	 * using Node Identity.
 	 */
-	bt_mesh_proxy_identity_enable();
-#endif
+	if (identity_enable) {
+		bt_mesh_proxy_identity_enable();
+	}
 
 done:
 	os_mbuf_free_chain(msg);
diff --git a/nimble/host/mesh/src/settings.c b/nimble/host/mesh/src/settings.c
new file mode 100644
index 00000000..5b103eaf
--- /dev/null
+++ b/nimble/host/mesh/src/settings.c
@@ -0,0 +1,1575 @@
+/*
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#define BT_DBG_ENABLED MYNEWT_VAL(BLE_MESH_DEBUG_SETTINGS)
+
+#include "mesh/mesh.h"
+#include "mesh/glue.h"
+#include "net.h"
+#include "crypto.h"
+#include "transport.h"
+#include "access.h"
+#include "foundation.h"
+#include "proxy.h"
+#include "settings.h"
+
+#include "config/config.h"
+
+/* Tracking of what storage changes are pending for App and Net Keys. We
+ * track this in a separate array here instead of within the respective
+ * bt_mesh_app_key and bt_mesh_subnet structs themselves, since once a key
+ * gets deleted its struct becomes invalid and may be reused for other keys.
+ */
+static struct key_update {
+	u16_t key_idx:12,    /* AppKey or NetKey Index */
+	      valid:1,       /* 1 if this entry is valid, 0 if not */
+	      app_key:1,     /* 1 if this is an AppKey, 0 if a NetKey */
+	      clear:1;       /* 1 if key needs clearing, 0 if storing */
+} key_updates[CONFIG_BT_MESH_APP_KEY_COUNT + CONFIG_BT_MESH_SUBNET_COUNT];
+
+static struct k_delayed_work pending_store;
+
+/* Mesh network storage information */
+struct net_val {
+	u16_t primary_addr;
+	u8_t  dev_key[16];
+} __packed;
+
+/* Sequence number storage */
+struct seq_val {
+	u8_t val[3];
+} __packed;
+
+/* Heartbeat Publication storage */
+struct hb_pub_val {
+	u16_t dst;
+	u8_t  period;
+	u8_t  ttl;
+	u16_t feat;
+	u16_t net_idx:12,
+	      indefinite:1;
+};
+
+/* Miscelaneous configuration server model states */
+struct cfg_val {
+	u8_t net_transmit;
+	u8_t relay;
+	u8_t relay_retransmit;
+	u8_t beacon;
+	u8_t gatt_proxy;
+	u8_t frnd;
+	u8_t default_ttl;
+};
+
+/* IV Index & IV Update storage */
+struct iv_val {
+	u32_t iv_index;
+	u8_t  iv_update:1,
+	      iv_duration:7;
+} __packed;
+
+/* Replay Protection List storage */
+struct rpl_val {
+	u32_t seq:24,
+	      old_iv:1;
+};
+
+/* NetKey storage information */
+struct net_key_val {
+	u8_t kr_flag:1,
+	     kr_phase:7;
+	u8_t val[2][16];
+} __packed;
+
+/* AppKey storage information */
+struct app_key_val {
+	u16_t net_idx;
+	bool  updated;
+	u8_t  val[2][16];
+} __packed;
+
+struct mod_pub_val {
+	u16_t addr;
+	u16_t key;
+	u8_t  ttl;
+	u8_t  retransmit;
+	u8_t  period;
+	u8_t  period_div:4,
+	      cred:1;
+};
+
+/* We need this so we don't overwrite app-hardcoded values in case FCB
+ * contains a history of changes but then has a NULL at the end.
+ */
+static struct {
+	bool valid;
+	struct cfg_val cfg;
+} stored_cfg;
+
+static int net_set(int argc, char **argv, char *val)
+{
+	struct net_val net;
+	int len, err;
+
+	BT_DBG("val %s", val ? val : "(null)");
+
+	if (!val) {
+		bt_mesh_comp_unprovision();
+		memset(bt_mesh.dev_key, 0, sizeof(bt_mesh.dev_key));
+		return 0;
+	}
+
+	len = sizeof(net);
+	err = settings_bytes_from_str(val, &net, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return err;
+	}
+
+	if (len != sizeof(net)) {
+		BT_ERR("Unexpected value length (%d != %zu)", len, sizeof(net));
+		return -EINVAL;
+	}
+
+	memcpy(bt_mesh.dev_key, net.dev_key, sizeof(bt_mesh.dev_key));
+	bt_mesh_comp_provision(net.primary_addr);
+
+	BT_DBG("Provisioned with primary address 0x%04x", net.primary_addr);
+	BT_DBG("Recovered DevKey %s", bt_hex(bt_mesh.dev_key, 16));
+
+	return 0;
+}
+
+static int iv_set(int argc, char **argv, char *val)
+{
+	struct iv_val iv;
+	int len, err;
+
+	BT_DBG("val %s", val ? val : "(null)");
+
+	if (!val) {
+		bt_mesh.iv_index = 0;
+		bt_mesh.iv_update = 0;
+		return 0;
+	}
+
+	len = sizeof(iv);
+	err = settings_bytes_from_str(val, &iv, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return err;
+	}
+
+	if (len != sizeof(iv)) {
+		BT_ERR("Unexpected value length (%d != %zu)", len, sizeof(iv));
+		return -EINVAL;
+	}
+
+	bt_mesh.iv_index = iv.iv_index;
+	bt_mesh.iv_update = iv.iv_update;
+	bt_mesh.ivu_duration = iv.iv_duration;
+
+	BT_DBG("IV Index 0x%04x (IV Update Flag %u) duration %u hours",
+	       bt_mesh.iv_index, bt_mesh.iv_update, bt_mesh.ivu_duration);
+
+	return 0;
+}
+
+static int seq_set(int argc, char **argv, char *val)
+{
+	struct seq_val seq;
+	int len, err;
+
+	BT_DBG("val %s", val ? val : "(null)");
+
+	if (!val) {
+		bt_mesh.seq = 0;
+		return 0;
+	}
+
+	len = sizeof(seq);
+	err = settings_bytes_from_str(val, &seq, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return err;
+	}
+
+	if (len != sizeof(seq)) {
+		BT_ERR("Unexpected value length (%d != %zu)", len, sizeof(seq));
+		return -EINVAL;
+	}
+
+	bt_mesh.seq = ((u32_t)seq.val[0] | ((u32_t)seq.val[1] << 8) |
+		       ((u32_t)seq.val[2] << 16));
+
+	if (CONFIG_BT_MESH_SEQ_STORE_RATE > 0) {
+		/* Make sure we have a large enough sequence number. We
+		 * subtract 1 so that the first transmission causes a write
+		 * to the settings storage.
+		 */
+		bt_mesh.seq += (CONFIG_BT_MESH_SEQ_STORE_RATE -
+				(bt_mesh.seq % CONFIG_BT_MESH_SEQ_STORE_RATE));
+		bt_mesh.seq--;
+	}
+
+	BT_DBG("Sequence Number 0x%06x", bt_mesh.seq);
+
+	return 0;
+}
+
+static struct bt_mesh_rpl *rpl_find(u16_t src)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) {
+		if (bt_mesh.rpl[i].src == src) {
+			return &bt_mesh.rpl[i];
+		}
+	}
+
+	return NULL;
+}
+
+static struct bt_mesh_rpl *rpl_alloc(u16_t src)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) {
+		if (!bt_mesh.rpl[i].src) {
+			bt_mesh.rpl[i].src = src;
+			return &bt_mesh.rpl[i];
+		}
+	}
+
+	return NULL;
+}
+
+static int rpl_set(int argc, char **argv, char *val)
+{
+	struct bt_mesh_rpl *entry;
+	struct rpl_val rpl;
+	int len, err;
+	u16_t src;
+
+	if (argc < 1) {
+		BT_ERR("Invalid argc (%d)", argc);
+		return -ENOENT;
+	}
+
+	BT_DBG("argv[0] %s val %s", argv[0], val ? val : "(null)");
+
+	src = strtol(argv[0], NULL, 16);
+	entry = rpl_find(src);
+
+	if (!val) {
+		if (entry) {
+			memset(entry, 0, sizeof(*entry));
+		} else {
+			BT_WARN("Unable to find RPL entry for 0x%04x", src);
+		}
+
+		return 0;
+	}
+
+	if (!entry) {
+		entry = rpl_alloc(src);
+		if (!entry) {
+			BT_ERR("Unable to allocate RPL entry for 0x%04x", src);
+			return -ENOMEM;
+		}
+	}
+
+	len = sizeof(rpl);
+	err = settings_bytes_from_str(val, &rpl, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return err;
+	}
+
+	if (len != sizeof(rpl)) {
+		BT_ERR("Unexpected value length (%d != %zu)", len, sizeof(rpl));
+		return -EINVAL;
+	}
+
+	entry->seq = rpl.seq;
+	entry->old_iv = rpl.old_iv;
+
+	BT_DBG("RPL entry for 0x%04x: Seq 0x%06x old_iv %u", entry->src,
+	       entry->seq, entry->old_iv);
+
+	return 0;
+}
+
+static int net_key_set(int argc, char **argv, char *val)
+{
+	struct bt_mesh_subnet *sub;
+	struct net_key_val key;
+	int len, i, err;
+	u16_t net_idx;
+
+	BT_DBG("argv[0] %s val %s", argv[0], val ? val : "(null)");
+
+	net_idx = strtol(argv[0], NULL, 16);
+	sub = bt_mesh_subnet_get(net_idx);
+
+	if (!val) {
+		if (!sub) {
+			BT_ERR("No subnet with NetKeyIndex 0x%03x", net_idx);
+			return -ENOENT;
+		}
+
+		BT_DBG("Deleting NetKeyIndex 0x%03x", net_idx);
+		bt_mesh_subnet_del(sub, false);
+		return 0;
+	}
+
+	len = sizeof(key);
+	err = settings_bytes_from_str(val, &key, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return err;
+	}
+
+	if (len != sizeof(key)) {
+		BT_ERR("Unexpected value length (%d != %zu)", len, sizeof(key));
+		return -EINVAL;
+	}
+
+	if (sub) {
+		BT_DBG("Updating existing NetKeyIndex 0x%03x", net_idx);
+
+		sub->kr_flag = key.kr_flag;
+		sub->kr_phase = key.kr_phase;
+		memcpy(sub->keys[0].net, &key.val[0], 16);
+		memcpy(sub->keys[1].net, &key.val[1], 16);
+
+		return 0;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
+		if (bt_mesh.sub[i].net_idx == BT_MESH_KEY_UNUSED) {
+			sub = &bt_mesh.sub[i];
+			break;
+		}
+	}
+
+	if (!sub) {
+		BT_ERR("No space to allocate a new subnet");
+		return -ENOMEM;
+	}
+
+	sub->net_idx = net_idx;
+	sub->kr_flag = key.kr_flag;
+	sub->kr_phase = key.kr_phase;
+	memcpy(sub->keys[0].net, &key.val[0], 16);
+	memcpy(sub->keys[1].net, &key.val[1], 16);
+
+	BT_DBG("NetKeyIndex 0x%03x recovered from storage", net_idx);
+
+	return 0;
+}
+
+static int app_key_set(int argc, char **argv, char *val)
+{
+	struct bt_mesh_app_key *app;
+	struct bt_mesh_subnet *sub;
+	struct app_key_val key;
+	u16_t app_idx;
+	int len, err;
+
+	BT_DBG("argv[0] %s val %s", argv[0], val ? val : "(null)");
+
+	app_idx = strtol(argv[0], NULL, 16);
+
+	if (!val) {
+		BT_DBG("Deleting AppKeyIndex 0x%03x", app_idx);
+
+		app = bt_mesh_app_key_find(app_idx);
+		if (app) {
+			bt_mesh_app_key_del(app, false);
+		}
+
+		return 0;
+	}
+
+	len = sizeof(key);
+	err = settings_bytes_from_str(val, &key, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return err;
+	}
+
+	if (len != sizeof(key)) {
+		BT_ERR("Unexpected value length (%d != %zu)", len, sizeof(key));
+		return -EINVAL;
+	}
+
+	sub = bt_mesh_subnet_get(key.net_idx);
+	if (!sub) {
+		BT_ERR("Failed to find subnet 0x%03x", key.net_idx);
+		return -ENOENT;
+	}
+
+	app = bt_mesh_app_key_find(app_idx);
+	if (!app) {
+		app = bt_mesh_app_key_alloc(app_idx);
+	}
+
+	if (!app) {
+		BT_ERR("No space for a new app key");
+		return -ENOMEM;
+	}
+
+	app->net_idx = key.net_idx;
+	app->app_idx = app_idx;
+	app->updated = key.updated;
+	memcpy(app->keys[0].val, key.val[0], 16);
+	memcpy(app->keys[1].val, key.val[1], 16);
+
+	bt_mesh_app_id(app->keys[0].val, &app->keys[0].id);
+	bt_mesh_app_id(app->keys[1].val, &app->keys[1].id);
+
+	BT_DBG("AppKeyIndex 0x%03x recovered from storage", app_idx);
+
+	return 0;
+}
+
+static int hb_pub_set(int argc, char **argv, char *val)
+{
+	struct bt_mesh_hb_pub *pub = bt_mesh_hb_pub_get();
+	struct hb_pub_val hb_val;
+	int len, err;
+
+	BT_DBG("val %s", val ? val : "(null)");
+
+	if (!pub) {
+		return -ENOENT;
+	}
+
+	if (!val) {
+		pub->dst = BT_MESH_ADDR_UNASSIGNED;
+		pub->count = 0;
+		pub->ttl = 0;
+		pub->period = 0;
+		pub->feat = 0;
+
+		BT_DBG("Cleared heartbeat publication");
+		return 0;
+	}
+
+	len = sizeof(hb_val);
+	err = settings_bytes_from_str(val, &hb_val, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return err;
+	}
+
+	if (len != sizeof(hb_val)) {
+		BT_ERR("Unexpected value length (%d != %zu)", len,
+		       sizeof(hb_val));
+		return -EINVAL;
+	}
+
+	pub->dst = hb_val.dst;
+	pub->period = hb_val.period;
+	pub->ttl = hb_val.ttl;
+	pub->feat = hb_val.feat;
+	pub->net_idx = hb_val.net_idx;
+
+	if (hb_val.indefinite) {
+		pub->count = 0xffff;
+	} else {
+		pub->count = 0;
+	}
+
+	BT_DBG("Restored heartbeat publication");
+
+	return 0;
+}
+
+static int cfg_set(int argc, char **argv, char *val)
+{
+	struct bt_mesh_cfg_srv *cfg = bt_mesh_cfg_get();
+	int len, err;
+
+	BT_DBG("val %s", val ? val : "(null)");
+
+	if (!cfg) {
+		return -ENOENT;
+	}
+
+	if (!val) {
+		stored_cfg.valid = false;
+		BT_DBG("Cleared configuration state");
+		return 0;
+	}
+
+	len = sizeof(stored_cfg.cfg);
+	err = settings_bytes_from_str(val, &stored_cfg.cfg, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return err;
+	}
+
+	if (len != sizeof(stored_cfg.cfg)) {
+		BT_ERR("Unexpected value length (%d != %zu)", len,
+		       sizeof(stored_cfg.cfg));
+		return -EINVAL;
+	}
+
+	stored_cfg.valid = true;
+	BT_DBG("Restored configuration state");
+
+	return 0;
+}
+
+static int mod_set_bind(struct bt_mesh_model *mod, char *val)
+{
+	int len, err, i;
+
+	/* Start with empty array regardless of cleared or set value */
+	for (i = 0; i < ARRAY_SIZE(mod->keys); i++) {
+		mod->keys[i] = BT_MESH_KEY_UNUSED;
+	}
+
+	if (!val) {
+		BT_DBG("Cleared bindings for model");
+		return 0;
+	}
+
+	len = sizeof(mod->keys);
+	err = settings_bytes_from_str(val, mod->keys, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return -EINVAL;
+	}
+
+	BT_DBG("Decoded %u bound keys for model", len / sizeof(mod->keys[0]));
+	return 0;
+}
+
+static int mod_set_sub(struct bt_mesh_model *mod, char *val)
+{
+	int len, err;
+
+	/* Start with empty array regardless of cleared or set value */
+	memset(mod->groups, 0, sizeof(mod->groups));
+
+	if (!val) {
+		BT_DBG("Cleared subscriptions for model");
+		return 0;
+	}
+
+	len = sizeof(mod->groups);
+	err = settings_bytes_from_str(val, mod->groups, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return -EINVAL;
+	}
+
+	BT_DBG("Decoded %u subscribed group addresses for model",
+	       len / sizeof(mod->groups[0]));
+	return 0;
+}
+
+static int mod_set_pub(struct bt_mesh_model *mod, char *val)
+{
+	struct mod_pub_val pub;
+	int len, err;
+
+	if (!mod->pub) {
+		BT_WARN("Model has no publication context!");
+		return -EINVAL;
+	}
+
+	if (!val) {
+		mod->pub->addr = BT_MESH_ADDR_UNASSIGNED;
+		mod->pub->key = 0;
+		mod->pub->cred = 0;
+		mod->pub->ttl = 0;
+		mod->pub->period = 0;
+		mod->pub->retransmit = 0;
+		mod->pub->count = 0;
+
+		BT_DBG("Cleared publication for model");
+		return 0;
+	}
+
+	len = sizeof(pub);
+	err = settings_bytes_from_str(val, &pub, &len);
+	if (err) {
+		BT_ERR("Failed to decode value %s (err %d)", val, err);
+		return -EINVAL;
+	}
+
+	if (len != sizeof(pub)) {
+		BT_ERR("Invalid length for model publication");
+		return -EINVAL;
+	}
+
+	mod->pub->addr = pub.addr;
+	mod->pub->key = pub.key;
+	mod->pub->cred = pub.cred;
+	mod->pub->ttl = pub.ttl;
+	mod->pub->period = pub.period;
+	mod->pub->retransmit = pub.retransmit;
+	mod->pub->count = 0;
+
+	BT_DBG("Restored model publication, dst 0x%04x app_idx 0x%03x",
+	       pub.addr, pub.key);
+
+	return 0;
+}
+
+static int mod_set(bool vnd, int argc, char **argv, char *val)
+{
+	struct bt_mesh_model *mod;
+	u8_t elem_idx, mod_idx;
+	u16_t mod_key;
+
+	if (argc < 2) {
+		BT_ERR("Too small argc (%d)", argc);
+		return -ENOENT;
+	}
+
+	mod_key = strtol(argv[0], NULL, 16);
+	elem_idx = mod_key >> 8;
+	mod_idx = mod_key;
+
+	BT_DBG("Decoded mod_key 0x%04x as elem_idx %u mod_idx %u",
+	       mod_key, elem_idx, mod_idx);
+
+	mod = bt_mesh_model_get(vnd, elem_idx, mod_idx);
+	if (!mod) {
+		BT_ERR("Failed to get model for elem_idx %u mod_idx %u",
+		       elem_idx, mod_idx);
+		return -ENOENT;
+	}
+
+	if (!strcmp(argv[1], "bind")) {
+		return mod_set_bind(mod, val);
+	}
+
+	if (!strcmp(argv[1], "sub")) {
+		return mod_set_sub(mod, val);
+	}
+
+	if (!strcmp(argv[1], "pub")) {
+		return mod_set_pub(mod, val);
+	}
+
+	BT_WARN("Unknown module key %s", argv[1]);
+	return -ENOENT;
+}
+
+static int sig_mod_set(int argc, char **argv, char *val)
+{
+	return mod_set(false, argc, argv, val);
+}
+
+static int vnd_mod_set(int argc, char **argv, char *val)
+{
+	return mod_set(true, argc, argv, val);
+}
+
+const struct mesh_setting {
+	const char *name;
+	int (*func)(int argc, char **argv, char *val);
+} settings[] = {
+	{ "Net", net_set },
+	{ "IV", iv_set },
+	{ "Seq", seq_set },
+	{ "RPL", rpl_set },
+	{ "NetKey", net_key_set },
+	{ "AppKey", app_key_set },
+	{ "HBPub", hb_pub_set },
+	{ "Cfg", cfg_set },
+	{ "s", sig_mod_set },
+	{ "v", vnd_mod_set },
+};
+
+static int mesh_set(int argc, char **argv, char *val)
+{
+	int i;
+
+	if (argc < 1) {
+		BT_ERR("Insufficient number of arguments");
+		return -EINVAL;
+	}
+
+	BT_DBG("argv[0] %s val %s", argv[0], val ? val : "(null)");
+
+	for (i = 0; i < ARRAY_SIZE(settings); i++) {
+		if (!strcmp(settings[i].name, argv[0])) {
+			argc--;
+			argv++;
+
+			return settings[i].func(argc, argv, val);
+		}
+	}
+
+	BT_WARN("No matching handler for key %s", argv[0]);
+
+	return -ENOENT;
+}
+
+static int subnet_init(struct bt_mesh_subnet *sub)
+{
+	int err;
+
+	err = bt_mesh_net_keys_create(&sub->keys[0], sub->keys[0].net);
+	if (err) {
+		BT_ERR("Unable to generate keys for subnet");
+		return -EIO;
+	}
+
+	if (sub->kr_phase != BT_MESH_KR_NORMAL) {
+		err = bt_mesh_net_keys_create(&sub->keys[1], sub->keys[1].net);
+		if (err) {
+			BT_ERR("Unable to generate keys for subnet");
+			memset(&sub->keys[0], 0, sizeof(sub->keys[0]));
+			return -EIO;
+		}
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) {
+		sub->node_id = BT_MESH_NODE_IDENTITY_STOPPED;
+	} else {
+		sub->node_id = BT_MESH_NODE_IDENTITY_NOT_SUPPORTED;
+	}
+
+	/* Make sure we have valid beacon data to be sent */
+	bt_mesh_net_beacon_update(sub);
+
+	return 0;
+}
+
+static void commit_mod(struct bt_mesh_model *mod, struct bt_mesh_elem *elem,
+		       bool vnd, bool primary, void *user_data)
+{
+	if (mod->pub && mod->pub->update &&
+	    mod->pub->addr != BT_MESH_ADDR_UNASSIGNED) {
+		s32_t ms = bt_mesh_model_pub_period_get(mod);
+		if (ms) {
+			BT_DBG("Starting publish timer (period %u ms)", ms);
+			k_delayed_work_submit(&mod->pub->timer, ms);
+		}
+	}
+}
+
+static int mesh_commit(void)
+{
+	struct bt_mesh_hb_pub *hb_pub;
+	struct bt_mesh_cfg_srv *cfg;
+	int i;
+
+	BT_DBG("sub[0].net_idx 0x%03x", bt_mesh.sub[0].net_idx);
+
+	if (bt_mesh.sub[0].net_idx == BT_MESH_KEY_UNUSED) {
+		/* Nothing to do since we're not yet provisioned */
+		return 0;
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) {
+		bt_mesh_proxy_prov_disable();
+	}
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
+		struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
+		int err;
+
+		if (sub->net_idx == BT_MESH_KEY_UNUSED) {
+			continue;
+		}
+
+		err = subnet_init(sub);
+		if (err) {
+			BT_ERR("Failed to init subnet 0x%03x", sub->net_idx);
+		}
+	}
+
+	if (bt_mesh.ivu_duration < BT_MESH_IVU_MIN_HOURS) {
+		k_delayed_work_submit(&bt_mesh.ivu_timer, BT_MESH_IVU_TIMEOUT);
+	}
+
+	bt_mesh_model_foreach(commit_mod, NULL);
+
+	hb_pub = bt_mesh_hb_pub_get();
+	if (hb_pub && hb_pub->dst != BT_MESH_ADDR_UNASSIGNED &&
+	    hb_pub->count && hb_pub->period) {
+		BT_DBG("Starting heartbeat publication");
+		k_work_submit(&hb_pub->timer.work);
+	}
+
+	cfg = bt_mesh_cfg_get();
+	if (cfg && stored_cfg.valid) {
+		cfg->net_transmit = stored_cfg.cfg.net_transmit;
+		cfg->relay = stored_cfg.cfg.relay;
+		cfg->relay_retransmit = stored_cfg.cfg.relay_retransmit;
+		cfg->beacon = stored_cfg.cfg.beacon;
+		cfg->gatt_proxy = stored_cfg.cfg.gatt_proxy;
+		cfg->frnd = stored_cfg.cfg.frnd;
+		cfg->default_ttl = stored_cfg.cfg.default_ttl;
+	}
+
+	bt_mesh.valid = 1;
+
+	bt_mesh_net_start();
+
+	return 0;
+}
+
+static void schedule_store(int flag)
+{
+	s32_t timeout;
+
+	atomic_set_bit(bt_mesh.flags, flag);
+
+	if (atomic_test_bit(bt_mesh.flags, BT_MESH_NET_PENDING) ||
+	    atomic_test_bit(bt_mesh.flags, BT_MESH_IV_PENDING) ||
+	    atomic_test_bit(bt_mesh.flags, BT_MESH_SEQ_PENDING)) {
+		timeout = K_NO_WAIT;
+	} else if (atomic_test_bit(bt_mesh.flags, BT_MESH_RPL_PENDING) &&
+		   (CONFIG_BT_MESH_RPL_STORE_TIMEOUT <
+		    CONFIG_BT_MESH_STORE_TIMEOUT)) {
+		timeout = K_SECONDS(CONFIG_BT_MESH_RPL_STORE_TIMEOUT);
+	} else {
+		timeout = K_SECONDS(CONFIG_BT_MESH_STORE_TIMEOUT);
+	}
+
+	BT_DBG("Waiting %d seconds", timeout / MSEC_PER_SEC);
+
+	k_delayed_work_submit(&pending_store, timeout);
+}
+
+static void clear_iv(void)
+{
+	BT_DBG("Clearing IV");
+	settings_save_one("bt_mesh/IV", NULL);
+}
+
+static void clear_net(void)
+{
+	BT_DBG("Clearing Network");
+	settings_save_one("bt_mesh/Net", NULL);
+}
+
+static void store_pending_net(void)
+{
+	char buf[BT_SETTINGS_SIZE(sizeof(struct net_val))];
+	struct net_val net;
+	char *str;
+
+	BT_DBG("addr 0x%04x DevKey %s", bt_mesh_primary_addr(),
+	       bt_hex(bt_mesh.dev_key, 16));
+
+	net.primary_addr = bt_mesh_primary_addr();
+	memcpy(net.dev_key, bt_mesh.dev_key, 16);
+
+	str = settings_str_from_bytes(&net, sizeof(net), buf, sizeof(buf));
+	if (!str) {
+		BT_ERR("Unable to encode Network as value");
+		return;
+	}
+
+	BT_DBG("Saving Network as value %s", str);
+	settings_save_one("bt_mesh/Net", str);
+}
+
+void bt_mesh_store_net(void)
+{
+	schedule_store(BT_MESH_NET_PENDING);
+}
+
+static void store_pending_iv(void)
+{
+	char buf[BT_SETTINGS_SIZE(sizeof(struct iv_val))];
+	struct iv_val iv;
+	char *str;
+
+	iv.iv_index = bt_mesh.iv_index;
+	iv.iv_update = bt_mesh.iv_update;
+	iv.iv_duration = bt_mesh.ivu_duration;
+
+	str = settings_str_from_bytes(&iv, sizeof(iv), buf, sizeof(buf));
+	if (!str) {
+		BT_ERR("Unable to encode IV as value");
+		return;
+	}
+
+	BT_DBG("Saving IV as value %s", str);
+	settings_save_one("bt_mesh/IV", str);
+}
+
+void bt_mesh_store_iv(bool only_duration)
+{
+	schedule_store(BT_MESH_IV_PENDING);
+
+	if (!only_duration) {
+		/* Always update Seq whenever IV changes */
+		schedule_store(BT_MESH_SEQ_PENDING);
+	}
+}
+
+static void store_pending_seq(void)
+{
+	char buf[BT_SETTINGS_SIZE(sizeof(struct seq_val))];
+	struct seq_val seq;
+	char *str;
+
+	seq.val[0] = bt_mesh.seq;
+	seq.val[1] = bt_mesh.seq >> 8;
+	seq.val[2] = bt_mesh.seq >> 16;
+
+	str = settings_str_from_bytes(&seq, sizeof(seq), buf, sizeof(buf));
+	if (!str) {
+		BT_ERR("Unable to encode Seq as value");
+		return;
+	}
+
+	BT_DBG("Saving Seq as value %s", str);
+	settings_save_one("bt_mesh/Seq", str);
+}
+
+void bt_mesh_store_seq(void)
+{
+	if (CONFIG_BT_MESH_SEQ_STORE_RATE &&
+	    (bt_mesh.seq % CONFIG_BT_MESH_SEQ_STORE_RATE)) {
+		return;
+	}
+
+	schedule_store(BT_MESH_SEQ_PENDING);
+}
+
+static void store_rpl(struct bt_mesh_rpl *entry)
+{
+	char buf[BT_SETTINGS_SIZE(sizeof(struct rpl_val))];
+	struct rpl_val rpl;
+	char path[18];
+	char *str;
+
+	BT_DBG("src 0x%04x seq 0x%06x old_iv %u", entry->src, entry->seq,
+	       entry->old_iv);
+
+	rpl.seq = entry->seq;
+	rpl.old_iv = entry->old_iv;
+
+	str = settings_str_from_bytes(&rpl, sizeof(rpl), buf, sizeof(buf));
+	if (!str) {
+		BT_ERR("Unable to encode RPL as value");
+		return;
+	}
+
+	snprintk(path, sizeof(path), "bt_mesh/RPL/%x", entry->src);
+
+	BT_DBG("Saving RPL %s as value %s", path, str);
+	settings_save_one(path, str);
+}
+
+static void clear_rpl(void)
+{
+	int i;
+
+	BT_DBG("");
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) {
+		struct bt_mesh_rpl *rpl = &bt_mesh.rpl[i];
+		char path[18];
+
+		if (!rpl->src) {
+			continue;
+		}
+
+		snprintk(path, sizeof(path), "bt_mesh/RPL/%x", rpl->src);
+		settings_save_one(path, NULL);
+
+		memset(rpl, 0, sizeof(*rpl));
+	}
+}
+
+static void store_pending_rpl(void)
+{
+	int i;
+
+	BT_DBG("");
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) {
+		struct bt_mesh_rpl *rpl = &bt_mesh.rpl[i];
+
+		if (rpl->store) {
+			rpl->store = false;
+			store_rpl(rpl);
+		}
+	}
+}
+
+static void store_pending_hb_pub(void)
+{
+	char buf[BT_SETTINGS_SIZE(sizeof(struct hb_pub_val))];
+	struct bt_mesh_hb_pub *pub = bt_mesh_hb_pub_get();
+	struct hb_pub_val val;
+	char *str;
+
+	if (!pub) {
+		return;
+	}
+
+	if (pub->dst == BT_MESH_ADDR_UNASSIGNED) {
+		str = NULL;
+	} else {
+		val.indefinite = (pub->count = 0xffff);
+		val.dst = pub->dst;
+		val.period = pub->period;
+		val.ttl = pub->ttl;
+		val.feat = pub->feat;
+		val.net_idx = pub->net_idx;
+
+		str = settings_str_from_bytes(&val, sizeof(val),
+					      buf, sizeof(buf));
+		if (!str) {
+			BT_ERR("Unable to encode hb pub as value");
+			return;
+		}
+	}
+
+	BT_DBG("Saving Heartbeat Publication as value %s",
+	       str ? str : "(null)");
+	settings_save_one("bt_mesh/HBPub", str);
+}
+
+static void store_pending_cfg(void)
+{
+	char buf[BT_SETTINGS_SIZE(sizeof(struct cfg_val))];
+	struct bt_mesh_cfg_srv *cfg = bt_mesh_cfg_get();
+	struct cfg_val val;
+	char *str;
+
+	if (!cfg) {
+		return;
+	}
+
+	val.net_transmit = cfg->net_transmit;
+	val.relay = cfg->relay;
+	val.relay_retransmit = cfg->relay_retransmit;
+	val.beacon = cfg->beacon;
+	val.gatt_proxy = cfg->gatt_proxy;
+	val.frnd = cfg->frnd;
+	val.default_ttl = cfg->default_ttl;
+
+	str = settings_str_from_bytes(&val, sizeof(val), buf, sizeof(buf));
+	if (!str) {
+		BT_ERR("Unable to encode configuration as value");
+		return;
+	}
+
+	BT_DBG("Saving configuration as value %s", str);
+	settings_save_one("bt_mesh/Cfg", str);
+}
+
+static void clear_cfg(void)
+{
+	BT_DBG("Clearing configuration");
+	settings_save_one("bt_mesh/Cfg", NULL);
+}
+
+static void clear_app_key(u16_t app_idx)
+{
+	char path[20];
+
+	BT_DBG("AppKeyIndex 0x%03x", app_idx);
+
+	snprintk(path, sizeof(path), "bt_mesh/AppKey/%x", app_idx);
+	settings_save_one(path, NULL);
+}
+
+static void clear_net_key(u16_t net_idx)
+{
+	char path[20];
+
+	BT_DBG("NetKeyIndex 0x%03x", net_idx);
+
+	snprintk(path, sizeof(path), "bt_mesh/NetKey/%x", net_idx);
+	settings_save_one(path, NULL);
+}
+
+static void store_net_key(struct bt_mesh_subnet *sub)
+{
+	char buf[BT_SETTINGS_SIZE(sizeof(struct net_key_val))];
+	struct net_key_val key;
+	char path[20];
+	char *str;
+
+	BT_DBG("NetKeyIndex 0x%03x NetKey %s", sub->net_idx,
+	       bt_hex(sub->keys[0].net, 16));
+
+	memcpy(&key.val[0], sub->keys[0].net, 16);
+	memcpy(&key.val[1], sub->keys[1].net, 16);
+	key.kr_flag = sub->kr_flag;
+	key.kr_phase = sub->kr_phase;
+
+	str = settings_str_from_bytes(&key, sizeof(key), buf, sizeof(buf));
+	if (!str) {
+		BT_ERR("Unable to encode NetKey as value");
+		return;
+	}
+
+	snprintk(path, sizeof(path), "bt_mesh/NetKey/%x", sub->net_idx);
+
+	BT_DBG("Saving NetKey %s as value %s", path, str);
+	settings_save_one(path, str);
+}
+
+static void store_app_key(struct bt_mesh_app_key *app)
+{
+	char buf[BT_SETTINGS_SIZE(sizeof(struct app_key_val))];
+	struct app_key_val key;
+	char path[20];
+	char *str;
+
+	key.net_idx = app->net_idx;
+	key.updated = app->updated;
+	memcpy(key.val[0], app->keys[0].val, 16);
+	memcpy(key.val[1], app->keys[1].val, 16);
+
+	str = settings_str_from_bytes(&key, sizeof(key), buf, sizeof(buf));
+	if (!str) {
+		BT_ERR("Unable to encode AppKey as value");
+		return;
+	}
+
+	snprintk(path, sizeof(path), "bt_mesh/AppKey/%x", app->app_idx);
+
+	BT_DBG("Saving AppKey %s as value %s", path, str);
+	settings_save_one(path, str);
+}
+
+static void store_pending_keys(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(key_updates); i++) {
+		struct key_update *update = &key_updates[i];
+
+		if (!update->valid) {
+			continue;
+		}
+
+		if (update->clear) {
+			if (update->app_key) {
+				clear_app_key(update->key_idx);
+			} else {
+				clear_net_key(update->key_idx);
+			}
+		} else {
+			if (update->app_key) {
+				struct bt_mesh_app_key *key;
+
+				key = bt_mesh_app_key_find(update->key_idx);
+				if (key) {
+					store_app_key(key);
+				} else {
+					BT_WARN("AppKeyIndex 0x%03x not found",
+					       update->key_idx);
+				}
+
+			} else {
+				struct bt_mesh_subnet *sub;
+
+				sub = bt_mesh_subnet_get(update->key_idx);
+				if (sub) {
+					store_net_key(sub);
+				} else {
+					BT_WARN("NetKeyIndex 0x%03x not found",
+					       update->key_idx);
+				}
+			}
+		}
+
+		update->valid = 0;
+	}
+}
+
+static void encode_mod_path(struct bt_mesh_model *mod, bool vnd,
+			    const char *key, char *path, size_t path_len)
+{
+	u16_t mod_key = (((u16_t)mod->elem_idx << 8) | mod->mod_idx);
+
+	if (vnd) {
+		snprintk(path, path_len, "bt_mesh/v/%x/%s", mod_key, key);
+	} else {
+		snprintk(path, path_len, "bt_mesh/s/%x/%s", mod_key, key);
+	}
+}
+
+static void store_pending_mod_bind(struct bt_mesh_model *mod, bool vnd)
+{
+	u16_t keys[CONFIG_BT_MESH_MODEL_KEY_COUNT];
+	char buf[BT_SETTINGS_SIZE(sizeof(keys))];
+	char path[20];
+	int i, count;
+	char *val;
+
+	for (i = 0, count = 0; i < ARRAY_SIZE(mod->keys); i++) {
+		if (mod->keys[i] != BT_MESH_KEY_UNUSED) {
+			keys[count++] = mod->keys[i];
+		}
+	}
+
+	if (count) {
+		val = settings_str_from_bytes(keys, count * sizeof(keys[0]),
+					      buf, sizeof(buf));
+		if (!val) {
+			BT_ERR("Unable to encode model bindings as value");
+			return;
+		}
+	} else {
+		val = NULL;
+	}
+
+	encode_mod_path(mod, vnd, "bind", path, sizeof(path));
+
+	BT_DBG("Saving %s as %s", path, val ? val : "(null)");
+	settings_save_one(path, val);
+}
+
+static void store_pending_mod_sub(struct bt_mesh_model *mod, bool vnd)
+{
+	u16_t groups[CONFIG_BT_MESH_MODEL_GROUP_COUNT];
+	char buf[BT_SETTINGS_SIZE(sizeof(groups))];
+	char path[20];
+	int i, count;
+	char *val;
+
+	for (i = 0, count = 0; i < ARRAY_SIZE(mod->groups); i++) {
+		if (mod->groups[i] != BT_MESH_ADDR_UNASSIGNED) {
+			groups[count++] = mod->groups[i];
+		}
+	}
+
+	if (count) {
+		val = settings_str_from_bytes(groups, count * sizeof(groups[0]),
+					      buf, sizeof(buf));
+		if (!val) {
+			BT_ERR("Unable to encode model subscription as value");
+			return;
+		}
+	} else {
+		val = NULL;
+	}
+
+	encode_mod_path(mod, vnd, "sub", path, sizeof(path));
+
+	BT_DBG("Saving %s as %s", path, val ? val : "(null)");
+	settings_save_one(path, val);
+}
+
+static void store_pending_mod_pub(struct bt_mesh_model *mod, bool vnd)
+{
+	char buf[BT_SETTINGS_SIZE(sizeof(struct mod_pub_val))];
+	struct mod_pub_val pub;
+	char path[20];
+	char *val;
+
+	if (!mod->pub || mod->pub->addr == BT_MESH_ADDR_UNASSIGNED) {
+		val = NULL;
+	} else {
+		pub.addr = mod->pub->addr;
+		pub.key = mod->pub->key;
+		pub.ttl = mod->pub->ttl;
+		pub.retransmit = mod->pub->retransmit;
+		pub.period = mod->pub->period;
+		pub.period_div = mod->pub->period_div;
+		pub.cred = mod->pub->cred;
+
+		val = settings_str_from_bytes(&pub, sizeof(pub),
+					      buf, sizeof(buf));
+		if (!val) {
+			BT_ERR("Unable to encode model publication as value");
+			return;
+		}
+	}
+
+	encode_mod_path(mod, vnd, "pub", path, sizeof(path));
+
+	BT_DBG("Saving %s as %s", path, val ? val : "(null)");
+	settings_save_one(path, val);
+}
+
+static void store_pending_mod(struct bt_mesh_model *mod,
+			      struct bt_mesh_elem *elem, bool vnd,
+			      bool primary, void *user_data)
+{
+	if (!mod->flags) {
+		return;
+	}
+
+	if (mod->flags & BT_MESH_MOD_BIND_PENDING) {
+		mod->flags &= ~BT_MESH_MOD_BIND_PENDING;
+		store_pending_mod_bind(mod, vnd);
+	}
+
+	if (mod->flags & BT_MESH_MOD_SUB_PENDING) {
+		mod->flags &= ~BT_MESH_MOD_SUB_PENDING;
+		store_pending_mod_sub(mod, vnd);
+	}
+
+	if (mod->flags & BT_MESH_MOD_PUB_PENDING) {
+		mod->flags &= ~BT_MESH_MOD_PUB_PENDING;
+		store_pending_mod_pub(mod, vnd);
+	}
+}
+
+static void store_pending(struct ble_npl_event *work)
+{
+	BT_DBG("");
+
+	if (atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_RPL_PENDING)) {
+		if (bt_mesh.valid) {
+			store_pending_rpl();
+		} else {
+			clear_rpl();
+		}
+	}
+
+	if (atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_KEYS_PENDING)) {
+		store_pending_keys();
+	}
+
+	if (atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_NET_PENDING)) {
+		if (bt_mesh.valid) {
+			store_pending_net();
+		} else {
+			clear_net();
+		}
+	}
+
+	if (atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_IV_PENDING)) {
+		if (bt_mesh.valid) {
+			store_pending_iv();
+		} else {
+			clear_iv();
+		}
+	}
+
+	if (atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_SEQ_PENDING)) {
+		store_pending_seq();
+	}
+
+	if (atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_HB_PUB_PENDING)) {
+		store_pending_hb_pub();
+	}
+
+	if (atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_CFG_PENDING)) {
+		if (bt_mesh.valid) {
+			store_pending_cfg();
+		} else {
+			clear_cfg();
+		}
+	}
+
+	if (atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_MOD_PENDING)) {
+		bt_mesh_model_foreach(store_pending_mod, NULL);
+	}
+}
+
+void bt_mesh_store_rpl(struct bt_mesh_rpl *entry)
+{
+	entry->store = true;
+	schedule_store(BT_MESH_RPL_PENDING);
+}
+
+static struct key_update *key_update_find(bool app_key, u16_t key_idx,
+					  struct key_update **free_slot)
+{
+	struct key_update *match;
+	int i;
+
+	match = NULL;
+	*free_slot = NULL;
+
+	for (i = 0; i < ARRAY_SIZE(key_updates); i++) {
+		struct key_update *update = &key_updates[i];
+
+		if (!update->valid) {
+			*free_slot = update;
+			continue;
+		}
+
+		if (update->app_key != app_key) {
+			continue;
+		}
+
+		if (update->key_idx == key_idx) {
+			match = update;
+		}
+	}
+
+	return match;
+}
+
+void bt_mesh_store_subnet(struct bt_mesh_subnet *sub)
+{
+	struct key_update *update, *free_slot;
+
+	BT_DBG("NetKeyIndex 0x%03x", sub->net_idx);
+
+	update = key_update_find(false, sub->net_idx, &free_slot);
+	if (update) {
+		update->clear = 0;
+		schedule_store(BT_MESH_KEYS_PENDING);
+		return;
+	}
+
+	if (!free_slot) {
+		store_net_key(sub);
+		return;
+	}
+
+	free_slot->valid = 1;
+	free_slot->key_idx = sub->net_idx;
+	free_slot->app_key = 0;
+	free_slot->clear = 0;
+
+	schedule_store(BT_MESH_KEYS_PENDING);
+}
+
+void bt_mesh_store_app_key(struct bt_mesh_app_key *key)
+{
+	struct key_update *update, *free_slot;
+
+	BT_DBG("AppKeyIndex 0x%03x", key->app_idx);
+
+	update = key_update_find(true, key->app_idx, &free_slot);
+	if (update) {
+		update->clear = 0;
+		schedule_store(BT_MESH_KEYS_PENDING);
+		return;
+	}
+
+	if (!free_slot) {
+		store_app_key(key);
+		return;
+	}
+
+	free_slot->valid = 1;
+	free_slot->key_idx = key->app_idx;
+	free_slot->app_key = 1;
+	free_slot->clear = 0;
+
+	schedule_store(BT_MESH_KEYS_PENDING);
+}
+
+void bt_mesh_store_hb_pub(void)
+{
+	schedule_store(BT_MESH_HB_PUB_PENDING);
+}
+
+void bt_mesh_store_cfg(void)
+{
+	schedule_store(BT_MESH_CFG_PENDING);
+}
+
+void bt_mesh_clear_net(void)
+{
+	schedule_store(BT_MESH_NET_PENDING);
+	schedule_store(BT_MESH_IV_PENDING);
+	schedule_store(BT_MESH_CFG_PENDING);
+}
+
+void bt_mesh_clear_subnet(struct bt_mesh_subnet *sub)
+{
+	struct key_update *update, *free_slot;
+
+	BT_DBG("NetKeyIndex 0x%03x", sub->net_idx);
+
+	update = key_update_find(false, sub->net_idx, &free_slot);
+	if (update) {
+		update->clear = 1;
+		schedule_store(BT_MESH_KEYS_PENDING);
+		return;
+	}
+
+	if (!free_slot) {
+		clear_net_key(sub->net_idx);
+		return;
+	}
+
+	free_slot->valid = 1;
+	free_slot->key_idx = sub->net_idx;
+	free_slot->app_key = 0;
+	free_slot->clear = 1;
+
+	schedule_store(BT_MESH_KEYS_PENDING);
+}
+
+void bt_mesh_clear_app_key(struct bt_mesh_app_key *key)
+{
+	struct key_update *update, *free_slot;
+
+	BT_DBG("AppKeyIndex 0x%03x", key->app_idx);
+
+	update = key_update_find(true, key->app_idx, &free_slot);
+	if (update) {
+		update->clear = 1;
+		schedule_store(BT_MESH_KEYS_PENDING);
+		return;
+	}
+
+	if (!free_slot) {
+		clear_app_key(key->app_idx);
+		return;
+	}
+
+	free_slot->valid = 1;
+	free_slot->key_idx = key->app_idx;
+	free_slot->app_key = 1;
+	free_slot->clear = 1;
+
+	schedule_store(BT_MESH_KEYS_PENDING);
+}
+
+void bt_mesh_clear_rpl(void)
+{
+	schedule_store(BT_MESH_RPL_PENDING);
+}
+
+void bt_mesh_store_mod_bind(struct bt_mesh_model *mod)
+{
+	mod->flags |= BT_MESH_MOD_BIND_PENDING;
+	schedule_store(BT_MESH_MOD_PENDING);
+}
+
+void bt_mesh_store_mod_sub(struct bt_mesh_model *mod)
+{
+	mod->flags |= BT_MESH_MOD_SUB_PENDING;
+	schedule_store(BT_MESH_MOD_PENDING);
+}
+
+void bt_mesh_store_mod_pub(struct bt_mesh_model *mod)
+{
+	mod->flags |= BT_MESH_MOD_PUB_PENDING;
+	schedule_store(BT_MESH_MOD_PENDING);
+}
+
+static struct conf_handler bt_mesh_settings_conf_handler = {
+	.ch_name = "bt_mesh",
+	.ch_get = NULL,
+	.ch_set = mesh_set,
+	.ch_commit = mesh_commit,
+	.ch_export = NULL,
+};
+
+void bt_mesh_settings_init(void)
+{
+	int rc;
+
+	rc = conf_register(&bt_mesh_settings_conf_handler);
+
+	SYSINIT_PANIC_ASSERT_MSG(rc == 0,
+				 "Failed to register bt_mesh_settings conf");
+
+	k_delayed_work_init(&pending_store, store_pending);
+}
diff --git a/nimble/host/mesh/src/settings.h b/nimble/host/mesh/src/settings.h
new file mode 100644
index 00000000..7bd02847
--- /dev/null
+++ b/nimble/host/mesh/src/settings.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2018 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+void bt_mesh_store_net(void);
+void bt_mesh_store_iv(bool only_duration);
+void bt_mesh_store_seq(void);
+void bt_mesh_store_rpl(struct bt_mesh_rpl *rpl);
+void bt_mesh_store_subnet(struct bt_mesh_subnet *sub);
+void bt_mesh_store_app_key(struct bt_mesh_app_key *key);
+void bt_mesh_store_hb_pub(void);
+void bt_mesh_store_cfg(void);
+void bt_mesh_store_mod_bind(struct bt_mesh_model *mod);
+void bt_mesh_store_mod_sub(struct bt_mesh_model *mod);
+void bt_mesh_store_mod_pub(struct bt_mesh_model *mod);
+
+void bt_mesh_clear_net(void);
+void bt_mesh_clear_subnet(struct bt_mesh_subnet *sub);
+void bt_mesh_clear_app_key(struct bt_mesh_app_key *key);
+void bt_mesh_clear_rpl(void);
+
+void bt_mesh_settings_init(void);
diff --git a/nimble/host/mesh/src/shell.c b/nimble/host/mesh/src/shell.c
index 25ad8c65..dddfcc27 100644
--- a/nimble/host/mesh/src/shell.c
+++ b/nimble/host/mesh/src/shell.c
@@ -27,15 +27,17 @@
 #include "net.h"
 #include "access.h"
 #include "mesh_priv.h"
+#include "lpn.h"
+#include "transport.h"
+#include "foundation.h"
+#include "testing.h"
+#include "settings.h"
+
 #if MYNEWT_VAL(BLE_MESH_SHELL_MODELS)
 #include "mesh/model_srv.h"
 #include "mesh/model_cli.h"
 #include "light_model.h"
 #endif
-#include "lpn.h"
-#include "transport.h"
-#include "foundation.h"
-#include "testing.h"
 
 /* This should be higher priority (lower value) than main task priority */
 #define BLE_MESH_SHELL_TASK_PRIO 126
@@ -656,7 +658,17 @@ int cmd_mesh_init(int argc, char *argv[])
 	}
 
 	printk("Mesh initialized\n");
-	printk("Use \"pb-adv on\" or \"pb-gatt on\" to enable advertising\n");
+
+	if (IS_ENABLED(CONFIG_SETTINGS)) {
+		settings_load();
+	}
+
+	if (bt_mesh_is_provisioned()) {
+		printk("Mesh network restored from flash\n");
+	} else {
+		printk("Use \"pb-adv on\" or \"pb-gatt on\" to enable"
+		       " advertising\n");
+	}
 
 #if MYNEWT_VAL(BLE_MESH_LOW_POWER)
 	bt_mesh_lpn_set_cb(lpn_cb);
@@ -1818,7 +1830,7 @@ static int cmd_provision(int argc, char *argv[])
 		iv_index = 0;
 	}
 
-	err = bt_mesh_provision(default_key, net_idx, 0, iv_index, 0, addr,
+	err = bt_mesh_provision(default_key, net_idx, 0, iv_index, addr,
 				default_key);
 	if (err) {
 		printk("Provisioning failed (err %d)\n", err);
diff --git a/nimble/host/mesh/src/testing.c b/nimble/host/mesh/src/testing.c
index e9fcc7be..096cff63 100644
--- a/nimble/host/mesh/src/testing.c
+++ b/nimble/host/mesh/src/testing.c
@@ -164,7 +164,7 @@ int bt_test_bind_app_key_to_model(struct bt_mesh_model *model, u16_t key_idx, u1
 {
 	struct bt_mesh_model *found_model;
 
-	found_model = bt_mesh_model_find(model->elem, id);
+	found_model = bt_mesh_model_find(bt_mesh_model_elem(model), id);
 	if (!found_model) {
 		return STATUS_INVALID_MODEL;
 	}
diff --git a/nimble/host/mesh/src/transport.c b/nimble/host/mesh/src/transport.c
index e7f658e9..702f11ca 100644
--- a/nimble/host/mesh/src/transport.c
+++ b/nimble/host/mesh/src/transport.c
@@ -24,6 +24,7 @@
 #include "friend.h"
 #include "access.h"
 #include "foundation.h"
+#include "settings.h"
 #include "transport.h"
 #include "testing.h"
 
@@ -103,9 +104,7 @@ static int send_unseg(struct bt_mesh_net_tx *tx, struct os_mbuf *sdu,
 	BT_DBG("src 0x%04x dst 0x%04x app_idx 0x%04x sdu_len %u",
 	       tx->src, tx->ctx->addr, tx->ctx->app_idx, sdu->om_len);
 
-	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA,
-				 BT_MESH_TRANSMIT_COUNT(tx->xmit),
-				 BT_MESH_TRANSMIT_INT(tx->xmit), BUF_TIMEOUT);
+	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, tx->xmit, BUF_TIMEOUT);
 	if (!buf) {
 		BT_ERR("Out of network buffers");
 		return -ENOBUFS;
@@ -343,9 +342,7 @@ static int send_seg(struct bt_mesh_net_tx *net_tx, struct os_mbuf *sdu,
 		u16_t len;
 		int err;
 
-		seg = bt_mesh_adv_create(BT_MESH_ADV_DATA,
-					 BT_MESH_TRANSMIT_COUNT(net_tx->xmit),
-					 BT_MESH_TRANSMIT_INT(net_tx->xmit),
+		seg = bt_mesh_adv_create(BT_MESH_ADV_DATA, net_tx->xmit,
 					 BUF_TIMEOUT);
 		if (!seg) {
 			BT_ERR("Out of segment buffers");
@@ -402,7 +399,8 @@ static int send_seg(struct bt_mesh_net_tx *net_tx, struct os_mbuf *sdu,
 		}
 	}
 
-	if (bt_mesh_lpn_established()) {
+	if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) &&
+	    bt_mesh_lpn_established()) {
 		bt_mesh_lpn_poll();
 	}
 
@@ -518,6 +516,11 @@ static bool is_replay(struct bt_mesh_net_rx *rx)
 {
 	int i;
 
+	/* Don't bother checking messages from ourselves */
+	if (rx->net_if == BT_MESH_NET_IF_LOCAL) {
+		return false;
+	}
+
 	for (i = 0; i < ARRAY_SIZE(bt_mesh.rpl); i++) {
 		struct bt_mesh_rpl *rpl = &bt_mesh.rpl[i];
 
@@ -526,6 +529,11 @@ static bool is_replay(struct bt_mesh_net_rx *rx)
 			rpl->src = rx->ctx.addr;
 			rpl->seq = rx->seq;
 			rpl->old_iv = rx->old_iv;
+
+			if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+				bt_mesh_store_rpl(rpl);
+			}
+
 			return false;
 		}
 
@@ -539,6 +547,11 @@ static bool is_replay(struct bt_mesh_net_rx *rx)
 			    rpl->seq < rx->seq) {
 				rpl->seq = rx->seq;
 				rpl->old_iv = rx->old_iv;
+
+				if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+					bt_mesh_store_rpl(rpl);
+				}
+
 				return false;
 			} else {
 				return true;
@@ -550,8 +563,8 @@ static bool is_replay(struct bt_mesh_net_rx *rx)
 	return true;
 }
 
-static int sdu_recv(struct bt_mesh_net_rx *rx, u8_t hdr, u8_t aszmic,
-		    struct os_mbuf *buf)
+static int sdu_recv(struct bt_mesh_net_rx *rx, u32_t seq, u8_t hdr,
+		    u8_t aszmic, struct os_mbuf *buf)
 {
 	struct os_mbuf *sdu =
 		NET_BUF_SIMPLE(MYNEWT_VAL(BLE_MESH_RX_SDU_MAX) - 4);
@@ -569,12 +582,13 @@ static int sdu_recv(struct bt_mesh_net_rx *rx, u8_t hdr, u8_t aszmic,
 	}
 
 	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && !rx->local_match) {
-		BT_DBG("Ignoring PDU for LPN 0x%04x of this Friend", rx->dst);
+		BT_DBG("Ignoring PDU for LPN 0x%04x of this Friend",
+		       rx->ctx.recv_dst);
 		return 0;
 	}
 
-	if (BT_MESH_ADDR_IS_VIRTUAL(rx->dst)) {
-		ad = bt_mesh_label_uuid_get(rx->dst);
+	if (BT_MESH_ADDR_IS_VIRTUAL(rx->ctx.recv_dst)) {
+		ad = bt_mesh_label_uuid_get(rx->ctx.recv_dst);
 	} else {
 		ad = NULL;
 	}
@@ -585,8 +599,9 @@ static int sdu_recv(struct bt_mesh_net_rx *rx, u8_t hdr, u8_t aszmic,
 	if (!AKF(&hdr)) {
 		net_buf_simple_init(sdu, 0);
 		err = bt_mesh_app_decrypt(bt_mesh.dev_key, true, aszmic, buf,
-					  sdu, ad, rx->ctx.addr, rx->dst,
-					  rx->seq, BT_MESH_NET_IVI_RX(rx));
+					  sdu, ad, rx->ctx.addr,
+					  rx->ctx.recv_dst, seq,
+					  BT_MESH_NET_IVI_RX(rx));
 		if (err) {
 			BT_ERR("Unable to decrypt with DevKey");
 			err = -EINVAL;
@@ -620,8 +635,9 @@ static int sdu_recv(struct bt_mesh_net_rx *rx, u8_t hdr, u8_t aszmic,
 
 		net_buf_simple_init(sdu, 0);
 		err = bt_mesh_app_decrypt(keys->val, false, aszmic, buf,
-					  sdu, ad, rx->ctx.addr, rx->dst,
-					  rx->seq, BT_MESH_NET_IVI_RX(rx));
+					  sdu, ad, rx->ctx.addr,
+					  rx->ctx.recv_dst, seq,
+					  BT_MESH_NET_IVI_RX(rx));
 		if (err) {
 			BT_WARN("Unable to decrypt with AppKey %u", i);
 			continue;
@@ -691,7 +707,7 @@ static int trans_ack(struct bt_mesh_net_rx *rx, u8_t hdr,
 	seq_zero = (seq_zero >> 2) & 0x1fff;
 
 	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && rx->friend_match) {
-		BT_DBG("Ack for LPN 0x%04x of this Friend", rx->dst);
+		BT_DBG("Ack for LPN 0x%04x of this Friend", rx->ctx.recv_dst);
 		/* Best effort - we don't have enough info for true SeqAuth */
 		*seq_auth = SEQ_AUTH(BT_MESH_NET_IVI_RX(rx), seq_zero);
 		return 0;
@@ -754,7 +770,7 @@ static int trans_heartbeat(struct bt_mesh_net_rx *rx,
 		return -EINVAL;
 	}
 
-	if (rx->dst != hb_sub_dst) {
+	if (rx->ctx.recv_dst != hb_sub_dst) {
 		BT_WARN("Ignoring heartbeat to non-subscribed destination");
 		return 0;
 	}
@@ -768,7 +784,7 @@ static int trans_heartbeat(struct bt_mesh_net_rx *rx,
 	       rx->ctx.addr, rx->ctx.recv_ttl, init_ttl, hops,
 	       (hops == 1) ? "" : "s", feat);
 
-	bt_mesh_heartbeat(rx->ctx.addr, rx->dst, hops, feat);
+	bt_mesh_heartbeat(rx->ctx.addr, rx->ctx.recv_dst, hops, feat);
 
 	return 0;
 }
@@ -852,7 +868,7 @@ static int trans_unseg(struct os_mbuf *buf, struct bt_mesh_net_rx *rx,
 
 	if (rx->local_match && is_replay(rx)) {
 		BT_WARN("Replay: src 0x%04x dst 0x%04x seq 0x%06x",
-			rx->ctx.addr, rx->dst, rx->seq);
+			rx->ctx.addr, rx->ctx.recv_dst, rx->seq);
 		return -EINVAL;
 	}
 
@@ -866,7 +882,7 @@ static int trans_unseg(struct os_mbuf *buf, struct bt_mesh_net_rx *rx,
 			return 0;
 		}
 
-		return sdu_recv(rx, hdr, 0, buf);
+		return sdu_recv(rx, rx->seq, hdr, 0, buf);
 	}
 }
 
@@ -905,9 +921,7 @@ int bt_mesh_ctl_send(struct bt_mesh_net_tx *tx, u8_t ctl_op, void *data,
 	       tx->ctx->addr, tx->ctx->send_ttl, ctl_op);
 	BT_DBG("len %zu: %s", data_len, bt_hex(data, data_len));
 
-	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA,
-				 BT_MESH_TRANSMIT_COUNT(tx->xmit),
-				 BT_MESH_TRANSMIT_INT(tx->xmit), BUF_TIMEOUT);
+	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, tx->xmit, BUF_TIMEOUT);
 	if (!buf) {
 		BT_ERR("Out of transport buffers");
 		return -ENOBUFS;
@@ -1046,7 +1060,8 @@ static struct seg_rx *seg_rx_find(struct bt_mesh_net_rx *net_rx,
 	for (i = 0; i < ARRAY_SIZE(seg_rx); i++) {
 		struct seg_rx *rx = &seg_rx[i];
 
-		if (rx->src != net_rx->ctx.addr || rx->dst != net_rx->dst) {
+		if (rx->src != net_rx->ctx.addr ||
+		    rx->dst != net_rx->ctx.recv_dst) {
 			continue;
 		}
 
@@ -1082,7 +1097,7 @@ static bool seg_rx_is_valid(struct seg_rx *rx, struct bt_mesh_net_rx *net_rx,
 		return false;
 	}
 
-	if (rx->src != net_rx->ctx.addr || rx->dst != net_rx->dst) {
+	if (rx->src != net_rx->ctx.addr || rx->dst != net_rx->ctx.recv_dst) {
 		BT_ERR("Invalid source or destination for segment");
 		return false;
 	}
@@ -1117,7 +1132,7 @@ static struct seg_rx *seg_rx_alloc(struct bt_mesh_net_rx *net_rx,
 		rx->hdr = *hdr;
 		rx->ttl = net_rx->ctx.send_ttl;
 		rx->src = net_rx->ctx.addr;
-		rx->dst = net_rx->dst;
+		rx->dst = net_rx->ctx.recv_dst;
 		rx->block = 0;
 
 		BT_DBG("New RX context. Block Complete 0x%08x",
@@ -1185,9 +1200,9 @@ static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx,
 
 		if (rx->block == BLOCK_COMPLETE(rx->seg_n)) {
 			BT_WARN("Got segment for already complete SDU");
-			send_ack(net_rx->sub, net_rx->dst, net_rx->ctx.addr,
-				 net_rx->ctx.send_ttl, seq_auth, rx->block,
-				 rx->obo);
+			send_ack(net_rx->sub, net_rx->ctx.recv_dst,
+				 net_rx->ctx.addr, net_rx->ctx.send_ttl,
+				 seq_auth, rx->block, rx->obo);
 			return -EALREADY;
 		}
 
@@ -1202,7 +1217,7 @@ static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx,
 	/* Bail out early if we're not ready to receive such a large SDU */
 	if (!sdu_len_is_ok(net_rx->ctl, seg_n)) {
 		BT_ERR("Too big incoming SDU length");
-		send_ack(net_rx->sub, net_rx->dst, net_rx->ctx.addr,
+		send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr,
 			 net_rx->ctx.send_ttl, seq_auth, 0,
 			 net_rx->friend_match);
 		return -EMSGSIZE;
@@ -1239,8 +1254,9 @@ static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx,
 
 		if (rx->buf->om_len > MYNEWT_VAL(BLE_MESH_RX_SDU_MAX)) {
 			BT_ERR("Too large SDU len");
-			send_ack(net_rx->sub, net_rx->dst, net_rx->ctx.addr,
-				 net_rx->ctx.send_ttl, seq_auth, 0, rx->obo);
+			send_ack(net_rx->sub, net_rx->ctx.recv_dst,
+				 net_rx->ctx.addr, net_rx->ctx.send_ttl,
+				 seq_auth, 0, rx->obo);
 			seg_rx_reset(rx, true);
 			return -EMSGSIZE;
 		}
@@ -1276,7 +1292,7 @@ static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx,
 
 	if (net_rx->local_match && is_replay(net_rx)) {
 		BT_WARN("Replay: src 0x%04x dst 0x%04x seq 0x%06x",
-			net_rx->ctx.addr, net_rx->dst, net_rx->seq);
+			net_rx->ctx.addr, net_rx->ctx.recv_dst, net_rx->seq);
 		/* Clear the segment's bit */
 		rx->block &= ~BIT(seg_o);
 		return -EINVAL;
@@ -1284,17 +1300,15 @@ static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx,
 
 	*pdu_type = BT_MESH_FRIEND_PDU_COMPLETE;
 
-	/* Set the correct sequence number to be used with the App Nonce */
-	net_rx->seq = (rx->seq_auth & 0xffffff);
-
 	k_delayed_work_cancel(&rx->ack);
-	send_ack(net_rx->sub, net_rx->dst, net_rx->ctx.addr,
+	send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr,
 		 net_rx->ctx.send_ttl, seq_auth, rx->block, rx->obo);
 
 	if (net_rx->ctl) {
 		err = ctl_recv(net_rx, *hdr, rx->buf, seq_auth);
 	} else {
-		err = sdu_recv(net_rx, *hdr, ASZMIC(hdr), rx->buf);
+		err = sdu_recv(net_rx, (rx->seq_auth & 0xffffff), *hdr,
+			       ASZMIC(hdr), rx->buf);
 	}
 
 	seg_rx_reset(rx, false);
@@ -1311,13 +1325,13 @@ int bt_mesh_trans_recv(struct os_mbuf *buf, struct bt_mesh_net_rx *rx)
 
 	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
 		rx->friend_match = bt_mesh_friend_match(rx->sub->net_idx,
-							rx->dst);
+							rx->ctx.recv_dst);
 	} else {
 		rx->friend_match = false;
 	}
 
 	BT_DBG("src 0x%04x dst 0x%04x seq 0x%08x friend_match %u",
-	       rx->ctx.addr, rx->dst, rx->seq, rx->friend_match);
+	       rx->ctx.addr, rx->ctx.recv_dst, rx->seq, rx->friend_match);
 
 	/* Remove network headers */
 	net_buf_simple_pull(buf, BT_MESH_NET_HDR_LEN);
@@ -1326,7 +1340,7 @@ int bt_mesh_trans_recv(struct os_mbuf *buf, struct bt_mesh_net_rx *rx)
 
 	if (IS_ENABLED(CONFIG_BT_TESTING)) {
 		bt_test_mesh_net_recv(rx->ctx.recv_ttl, rx->ctl, rx->ctx.addr,
-				      rx->dst, buf->om_data, buf->om_len);
+				      rx->ctx.recv_dst, buf->om_data, buf->om_len);
 	}
 
 	/* If LPN mode is enabled messages are only accepted when we've
@@ -1397,6 +1411,12 @@ void bt_mesh_rx_reset(void)
 	for (i = 0; i < ARRAY_SIZE(seg_rx); i++) {
 		seg_rx_reset(&seg_rx[i], true);
 	}
+
+	if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
+		bt_mesh_clear_rpl();
+	} else {
+		memset(bt_mesh.rpl, 0, sizeof(bt_mesh.rpl));
+	}
 }
 
 void bt_mesh_tx_reset(void)
diff --git a/nimble/host/mesh/syscfg.yml b/nimble/host/mesh/syscfg.yml
index 20ff0ae1..831fd92e 100644
--- a/nimble/host/mesh/syscfg.yml
+++ b/nimble/host/mesh/syscfg.yml
@@ -126,6 +126,30 @@ syscfg.defs:
             4-byte MIC and 52 bytes using an 8-byte MIC.
         value: 10
 
+    BLE_MESH_IVU_DIVIDER:
+        description: >
+            When the IV Update state enters Normal operation or IV Update
+            in Progress, we need to keep track of how many hours has passed
+            in the state, since the specification requires us to remain in
+            the state at least for 96 hours (Update in Progress has an
+            additional upper limit of 144 hours).
+
+            In order to fulfil the above requirement, even if the node might
+            be powered off once in a while, we need to store persistently
+            how many hours the node has been in the state. This doesn't
+            necessarily need to happen every hour (thanks to the flexible
+            duration range). The exact cadence will depend a lot on the
+            ways that the node will be used and what kind of power source it
+            has.
+
+            Since there is no single optimal answer, this configuration
+            option allows specifying a divider, i.e. how many intervals
+            the 96 hour minimum gets split into. After each interval the
+            duration that the node has been in the current state gets
+            stored to flash. E.g. the default value of 4 means that the
+            state is saved every 24 hours (96 / 4).
+        value: 4
+
     BLE_MESH_TX_SEG_MSG_COUNT:
         description: >
             Maximum number of simultaneous outgoing multi-segment and/or
@@ -366,6 +390,11 @@ syscfg.defs:
             Use this option to enable Proxy protocol debug logs.
         value: 0
 
+    BLE_MESH_DEBUG_SETTINGS:
+        description: >
+            Use this option to enable persistent settings debug logs.
+        value: 1
+
     BLE_MESH_IV_UPDATE_TEST:
         description: >
             This option removes the 96 hour limit of the IV Update
@@ -418,6 +447,45 @@ syscfg.defs:
             Input OOB size
         value: 4
 
+    BLE_MESH_SETTINGS:
+        description: >
+            This option enables Mesh settings storage.
+        value: 1
+
+    BLE_MESH_STORE_TIMEOUT:
+        description: >
+            This value defines in seconds how soon any pending changes
+            are actually written into persistent storage (flash) after
+            a change occurs.
+        value: 2
+
+    BLE_MESH_SEQ_STORE_RATE:
+        description: >
+            This value defines how often the local sequence number gets
+            updated in persistent storage (i.e. flash). E.g. a value of 100
+            means that the sequence number will be stored to flash on every
+            100th increment. If the node sends messages very frequently a
+            higher value makes more sense, whereas if the node sends
+            infrequently a value as low as 0 (update storage for every
+            increment) can make sense. When the stack gets initialized it
+            will add this number to the last stored one, so that it starts
+            off with a value that's guaranteed to be larger than the last
+            one used before power off.
+        value: 128
+
+    BLE_MESH_RPL_STORE_TIMEOUT:
+        description: >
+            This value defines in seconds how soon the RPL gets written to
+            persistent storage after a change occurs. If the node receives
+            messages frequently it may make sense to have this set to a
+            large value, whereas if the RPL gets updated infrequently a
+            value as low as 0 (write immediately) may make sense. Note that
+            if the node operates a security sensitive use case, and there's
+            a risk of sudden power loss, it may be a security vulnerability
+            to set this value to anything else than 0 (a power loss before
+            writing to storage exposes the node to potential message
+            replay attacks).
+        value: 5
 
 syscfg.vals.BLE_MESH_SHELL:
     BLE_MESH_CFG_CLI: 1


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services