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 2017/12/12 08:53:08 UTC

[GitHub] michal-narajowski closed pull request #693: Complete Friend support ported from Zephyr

michal-narajowski closed pull request #693: Complete Friend support ported from Zephyr
URL: https://github.com/apache/mynewt-core/pull/693
 
 
   

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 8161b9b70..602b88139 100755
--- a/apps/blemesh/src/main.c
+++ b/apps/blemesh/src/main.c
@@ -24,6 +24,7 @@
 #include "hal/hal_system.h"
 #include "hal/hal_gpio.h"
 #include "bsp/bsp.h"
+#include "shell/shell.h"
 
 /* BLE */
 #include "nimble/ble.h"
@@ -43,10 +44,14 @@ static int recent_test_id = STANDARD_TEST_ID;
 
 static bool has_reg_fault = true;
 
-static struct bt_mesh_cfg cfg_srv = {
+static struct bt_mesh_cfg_srv cfg_srv = {
     .relay = BT_MESH_RELAY_DISABLED,
     .beacon = BT_MESH_BEACON_ENABLED,
-    .frnd = BT_MESH_FRIEND_NOT_SUPPORTED,
+#if MYNEWT_VAL(BLE_MESH_FRIEND)
+    .frnd = BT_MESH_FRIEND_ENABLED,
+#else
+    .gatt_proxy = BT_MESH_GATT_PROXY_NOT_SUPPORTED,
+#endif
 #if MYNEWT_VAL(BLE_MESH_GATT_PROXY)
     .gatt_proxy = BT_MESH_GATT_PROXY_ENABLED,
 #else
@@ -136,13 +141,25 @@ fault_test(struct bt_mesh_model *model, uint8_t test_id, uint16_t company_id)
     return 0;
 }
 
-static struct bt_mesh_health health_srv = {
-        .fault_get_cur = &fault_get_cur,
-        .fault_get_reg = &fault_get_reg,
-        .fault_clear = &fault_clear,
-        .fault_test = &fault_test,
+static const struct bt_mesh_health_srv_cb health_srv_cb = {
+    .fault_get_cur = &fault_get_cur,
+    .fault_get_reg = &fault_get_reg,
+    .fault_clear = &fault_clear,
+    .fault_test = &fault_test,
 };
 
+static struct bt_mesh_health_srv health_srv = {
+    .cb = &health_srv_cb,
+};
+
+static struct bt_mesh_model_pub health_pub;
+
+static void
+health_pub_init(void)
+{
+    health_pub.msg  = BT_MESH_HEALTH_FAULT_MSG(0);
+}
+
 static struct bt_mesh_model_pub gen_level_pub;
 static struct bt_mesh_model_pub gen_onoff_pub;
 
@@ -313,7 +330,7 @@ static const struct bt_mesh_model_op gen_level_op[] = {
 
 static struct bt_mesh_model root_models[] = {
     BT_MESH_MODEL_CFG_SRV(&cfg_srv),
-    BT_MESH_MODEL_HEALTH_SRV(&health_srv),
+    BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
     BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_ONOFF_SRV, gen_onoff_op,
               &gen_onoff_pub, NULL),
     BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_LEVEL_SRV, gen_level_op,
@@ -335,16 +352,16 @@ static const struct bt_mesh_comp comp = {
     .elem_count = ARRAY_SIZE(elements),
 };
 
-static int output_number(bt_mesh_output_action action, uint32_t number)
+static int output_number(bt_mesh_output_action_t action, uint32_t number)
 {
     console_printf("OOB Number: %lu\n", number);
 
     return 0;
 }
 
-static void prov_complete(void)
+static void prov_complete(u16_t net_idx, u16_t addr)
 {
-    console_printf("Provisioning completed\n");
+    console_printf("Local node provisioned, primary address 0x%04x\n", addr);
 }
 
 static const uint8_t dev_uuid[16] = MYNEWT_VAL(BLE_MESH_DEV_UUID);
@@ -383,6 +400,8 @@ blemesh_on_sync(void)
         return;
     }
 
+    shell_register_default_module("mesh");
+
     console_printf("Mesh initialized\n");
 }
 
@@ -402,6 +421,7 @@ main(void)
     hal_gpio_init_out(LED_2, 0);
 
     bt_mesh_register_gatt();
+    health_pub_init();
 
     while (1) {
         os_eventq_run(os_eventq_dflt_get());
diff --git a/apps/blemesh/syscfg.yml b/apps/blemesh/syscfg.yml
index eb087f892..c109e6ea6 100644
--- a/apps/blemesh/syscfg.yml
+++ b/apps/blemesh/syscfg.yml
@@ -46,4 +46,3 @@ syscfg.vals:
     BLE_MESH_DEBUG_LOW_POWER: 1
     BLE_MESH_DEBUG_FRIEND: 1
     BLE_MESH_DEBUG_PROXY: 1
-
diff --git a/net/nimble/host/mesh/include/mesh/access.h b/net/nimble/host/mesh/include/mesh/access.h
new file mode 100644
index 000000000..f41c8f1a8
--- /dev/null
+++ b/net/nimble/host/mesh/include/mesh/access.h
@@ -0,0 +1,401 @@
+/** @file
+ *  @brief Bluetooth Mesh Access Layer APIs.
+ */
+
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef __BT_MESH_ACCESS_H
+#define __BT_MESH_ACCESS_H
+
+/**
+ * @brief Bluetooth Mesh Access Layer
+ * @defgroup bt_mesh_access Bluetooth Mesh Access Layer
+ * @ingroup bt_mesh
+ * @{
+ */
+
+#define BT_MESH_ADDR_UNASSIGNED   0x0000
+#define BT_MESH_ADDR_ALL_NODES    0xffff
+#define BT_MESH_ADDR_PROXIES      0xfffc
+#define BT_MESH_ADDR_FRIENDS      0xfffd
+#define BT_MESH_ADDR_RELAYS       0xfffe
+
+#define BT_MESH_KEY_UNUSED        0xffff
+#define BT_MESH_KEY_DEV           0xfffe
+
+/** Helper to define a mesh element within an array.
+ *
+ *  In case the element has no SIG or Vendor models the helper
+ *  macro BT_MESH_MODEL_NONE can be given instead.
+ *
+ *  @param _loc       Location Descriptor.
+ *  @param _mods      Array of models.
+ *  @param _vnd_mods  Array of vendor models.
+ */
+#define BT_MESH_ELEM(_loc, _mods, _vnd_mods)        \
+{                                                   \
+	.loc              = (_loc),                 \
+	.model_count      = ARRAY_SIZE(_mods),      \
+	.models           = (_mods),                \
+	.vnd_model_count  = ARRAY_SIZE(_vnd_mods),  \
+	.vnd_models       = (_vnd_mods),            \
+}
+
+/** Abstraction that describes a Mesh Element */
+struct bt_mesh_elem {
+	/* Unicast Address. Set at runtime during provisioning. */
+	u16_t addr;
+
+	/* Location Descriptor (GATT Bluetooth Namespace Descriptors) */
+	const u16_t loc;
+
+	const u8_t model_count;
+	const u8_t vnd_model_count;
+
+	struct bt_mesh_model * const models;
+	struct bt_mesh_model * const vnd_models;
+};
+
+/* Foundation Models */
+#define BT_MESH_MODEL_ID_CFG_SRV                   0x0000
+#define BT_MESH_MODEL_ID_CFG_CLI                   0x0001
+#define BT_MESH_MODEL_ID_HEALTH_SRV                0x0002
+#define BT_MESH_MODEL_ID_HEALTH_CLI                0x0003
+
+/* Models from the Mesh Model Specification */
+#define BT_MESH_MODEL_ID_GEN_ONOFF_SRV             0x1000
+#define BT_MESH_MODEL_ID_GEN_ONOFF_CLI             0x1001
+#define BT_MESH_MODEL_ID_GEN_LEVEL_SRV             0x1002
+#define BT_MESH_MODEL_ID_GEN_LEVEL_CLI             0x1003
+#define BT_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV    0x1004
+#define BT_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI    0x1005
+#define BT_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV       0x1006
+#define BT_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV 0x1007
+#define BT_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI       0x1008
+#define BT_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV       0x1009
+#define BT_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV 0x100a
+#define BT_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI       0x100b
+#define BT_MESH_MODEL_ID_GEN_BATTERY_SRV           0x100c
+#define BT_MESH_MODEL_ID_GEN_BATTERY_CLI           0x100d
+#define BT_MESH_MODEL_ID_GEN_LOCATION_SRV          0x100e
+#define BT_MESH_MODEL_ID_GEN_LOCATION_SETUPSRV     0x100f
+#define BT_MESH_MODEL_ID_GEN_LOCATION_CLI          0x1010
+#define BT_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV        0x1011
+#define BT_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV 0x1012
+#define BT_MESH_MODEL_ID_GEN_USER_PROP_SRV         0x1013
+#define BT_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV       0x1014
+#define BT_MESH_MODEL_ID_GEN_PROP_CLI              0x1015
+#define BT_MESH_MODEL_ID_SENSOR_SRV                0x1100
+#define BT_MESH_MODEL_ID_SENSOR_SETUP_SRV          0x1101
+#define BT_MESH_MODEL_ID_SENSOR_CLI                0x1102
+#define BT_MESH_MODEL_ID_TIME_SRV                  0x1200
+#define BT_MESH_MODEL_ID_TIME_SETUP_SRV            0x1201
+#define BT_MESH_MODEL_ID_TIME_CLI                  0x1202
+#define BT_MESH_MODEL_ID_SCENE_SRV                 0x1203
+#define BT_MESH_MODEL_ID_SCENE_SETUP_SRV           0x1204
+#define BT_MESH_MODEL_ID_SCENE_CLI                 0x1205
+#define BT_MESH_MODEL_ID_SCHEDULER_SRV             0x1206
+#define BT_MESH_MODEL_ID_SCHEDULER_SETUP_SRV       0x1207
+#define BT_MESH_MODEL_ID_SCHEDULER_CLI             0x1208
+#define BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV       0x1300
+#define BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV 0x1301
+#define BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI       0x1302
+#define BT_MESH_MODEL_ID_LIGHT_CTL_SRV             0x1303
+#define BT_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV       0x1304
+#define BT_MESH_MODEL_ID_LIGHT_CTL_CLI             0x1305
+#define BT_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV        0x1306
+#define BT_MESH_MODEL_ID_LIGHT_HSL_SRV             0x1307
+#define BT_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV       0x1308
+#define BT_MESH_MODEL_ID_LIGHT_HSL_CLI             0x1309
+#define BT_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV         0x130a
+#define BT_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV         0x130b
+#define BT_MESH_MODEL_ID_LIGHT_XYL_SRV             0x130c
+#define BT_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV       0x130d
+#define BT_MESH_MODEL_ID_LIGHT_XYL_CLI             0x130e
+#define BT_MESH_MODEL_ID_LIGHT_LC_SRV              0x130f
+#define BT_MESH_MODEL_ID_LIGHT_LC_SETUPSRV         0x1310
+#define BT_MESH_MODEL_ID_LIGHT_LC_CLI              0x1311
+
+/** Message sending context. */
+struct bt_mesh_msg_ctx {
+	/** NetKey Index of the subnet to send the message on. */
+	u16_t net_idx;
+
+	/** AppKey Index to encrypt the message with. */
+	u16_t app_idx;
+
+	/** Remote address. */
+	u16_t addr;
+
+	/** Received TTL value. Not used for sending. */
+	u8_t  recv_ttl:7;
+
+	/** Force sending reliably by using segment acknowledgement */
+	u8_t  send_rel:1;
+
+	/** TTL, or BT_MESH_TTL_DEFAULT for default TTL. */
+	u8_t  send_ttl;
+};
+
+struct bt_mesh_model_op {
+	/* OpCode encoded using the BT_MESH_MODEL_OP_* macros */
+	const u32_t  opcode;
+
+	/* Minimum required message length */
+	const size_t min_len;
+
+	/* Message handler for the opcode */
+	void (*const func)(struct bt_mesh_model *model,
+			   struct bt_mesh_msg_ctx *ctx,
+			   struct os_mbuf *buf);
+};
+
+#define BT_MESH_MODEL_OP_1(b0) (b0)
+#define BT_MESH_MODEL_OP_2(b0, b1) (((b0) << 8) | (b1))
+#define BT_MESH_MODEL_OP_3(b0, cid) ((((b0) << 16) | 0xc00000) | (cid))
+
+#define BT_MESH_MODEL_OP_END { 0, 0, NULL }
+#define BT_MESH_MODEL_NO_OPS ((struct bt_mesh_model_op []) \
+			      { BT_MESH_MODEL_OP_END })
+
+/** Helper to define an empty model array */
+#define BT_MESH_MODEL_NONE ((struct bt_mesh_model []){})
+
+#define BT_MESH_MODEL(_id, _op, _pub, _user_data)                            \
+{                                                                            \
+	.id = (_id),                                                         \
+	.op = _op,                                                           \
+	.keys = { [0 ... (CONFIG_BT_MESH_MODEL_KEY_COUNT - 1)] =             \
+			BT_MESH_KEY_UNUSED },                                \
+	.pub = _pub,                                                         \
+	.groups = { [0 ... (CONFIG_BT_MESH_MODEL_GROUP_COUNT - 1)] =         \
+			BT_MESH_ADDR_UNASSIGNED },                           \
+	.user_data = _user_data,                                             \
+}
+
+#define BT_MESH_MODEL_VND(_company, _id, _op, _pub, _user_data)              \
+{                                                                            \
+	.vnd.company = (_company),                                           \
+	.vnd.id = (_id),                                                     \
+	.op = _op,                                                           \
+	.pub = _pub,                                                         \
+	.keys = { [0 ... (CONFIG_BT_MESH_MODEL_KEY_COUNT - 1)] =             \
+			BT_MESH_KEY_UNUSED },                                \
+	.groups = { [0 ... (CONFIG_BT_MESH_MODEL_GROUP_COUNT - 1)] =         \
+			BT_MESH_ADDR_UNASSIGNED },                           \
+	.user_data = _user_data,                                             \
+}
+
+/** @def BT_MESH_TRANSMIT
+ *
+ *  @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.
+ *
+ *  @return Mesh transmit value that can be used e.g. for the default
+ *          values of the configuration model data.
+ */
+#define BT_MESH_TRANSMIT(count, int_ms) ((count) | (((int_ms / 10) - 1) << 3))
+
+/** @def BT_MESH_TRANSMIT_COUNT
+ *
+ *  @brief Decode transmit count from a transmit value.
+ *
+ *  @param transmit Encoded transmit count & interval value.
+ *
+ *  @return Transmission count (actual transmissions is N + 1).
+ */
+#define BT_MESH_TRANSMIT_COUNT(transmit) (((transmit) & (u8_t)BIT_MASK(3)))
+
+/** @def BT_MESH_TRANSMIT_INT
+ *
+ *  @brief Decode transmit interval from a transmit value.
+ *
+ *  @param transmit Encoded transmit count & interval value.
+ *
+ *  @return Transmission interval in milliseconds.
+ */
+#define BT_MESH_TRANSMIT_INT(transmit) ((((transmit) >> 3) + 1) * 10)
+
+/** @def BT_MESH_PUB_TRANSMIT
+ *
+ *  @brief Encode Publish Retransmit 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 50.
+ *
+ *  @return Mesh transmit value that can be used e.g. for the default
+ *          values of the configuration model data.
+ */
+#define BT_MESH_PUB_TRANSMIT(count, int_ms) BT_MESH_TRANSMIT(count,           \
+							     (int_ms) / 5)
+
+/** @def BT_MESH_PUB_TRANSMIT_COUNT
+ *
+ *  @brief Decode Pubhlish Retransmit count from a given value.
+ *
+ *  @param transmit Encoded Publish Retransmit count & interval value.
+ *
+ *  @return Retransmission count (actual transmissions is N + 1).
+ */
+#define BT_MESH_PUB_TRANSMIT_COUNT(transmit) BT_MESH_TRANSMIT_COUNT(transmit)
+
+/** @def BT_MESH_PUB_TRANSMIT_INT
+ *
+ *  @brief Decode Publish Retransmit interval from a given value.
+ *
+ *  @param transmit Encoded Publish Retransmit count & interval value.
+ *
+ *  @return Transmission interval in milliseconds.
+ */
+#define BT_MESH_PUB_TRANSMIT_INT(transmit) ((((transmit) >> 3) + 1) * 50)
+
+/** Model publication context. */
+struct bt_mesh_model_pub {
+	/** The model the context belongs to. Initialized by the stack. */
+	struct bt_mesh_model *mod;
+
+	u16_t addr;         /**< Publish Address. */
+	u16_t key;          /**< Publish AppKey Index. */
+
+	u8_t  ttl;          /**< Publish Time to Live. */
+	u8_t  retransmit;   /**< Retransmit Count & Interval Steps. */
+	u8_t  period;       /**< Publish Period. */
+	u8_t  period_div:4, /**< Divisor for the Period. */
+	      cred:1,       /**< Friendship Credentials Flag. */
+	      count:3;      /**< Retransmissions left. */
+
+	u32_t period_start; /**< Start of the current period. */
+
+	/** @brief Publication buffer, containing the publication message.
+	 *
+	 *  The application is expected to initialize this with
+	 *  a valid net_buf_simple pointer, with the help of e.g.
+	 *  the NET_BUF_SIMPLE() macro. The publication buffer must
+	 *  contain a valid publication message before calling the
+	 *  bt_mesh_model_publish() API or after the publication's
+	 *  @ref bt_mesh_model_pub.update callback has been called
+	 *  and returned success. The buffer must be created outside
+	 *  of function context, i.e. it must not be on the stack.
+	 *  This is most conveniently acheived by creating it inline
+	 *  when declaring the publication context:
+	 *
+	 *      static struct bt_mesh_model_pub my_pub = {
+	 *              .msg = NET_BUF_SIMPLE(size),
+	 *      };
+	 */
+	struct os_mbuf *msg;
+
+	/** @brief Callback for updating the publication buffer.
+	 *
+	 *  When set to NULL, the model is assumed not to support
+	 *  periodic publishing. When set to non-NULL the callback
+	 *  will be called periodically and is expected to update
+	 *  @ref bt_mesh_model_pub.msg with a valid publication
+	 *  message.
+	 *
+	 *  @param mod The Model the Publication Context belogs to.
+	 *
+	 *  @return Zero on success or (negative) error code otherwise.
+	 */
+	int (*update)(struct bt_mesh_model *mod);
+
+	/** Publish Period Timer. Only for stack-internal use. */
+	struct k_delayed_work timer;
+};
+
+/** Abstraction that describes a Mesh Model instance */
+struct bt_mesh_model {
+	union {
+		const u16_t id;
+		struct {
+			u16_t company;
+			u16_t id;
+		} vnd;
+	};
+
+	/* The Element this Model belongs to */
+	struct bt_mesh_elem *elem;
+
+	/* Model Publication */
+	struct bt_mesh_model_pub * const pub;
+
+	/* AppKey List */
+	u16_t keys[CONFIG_BT_MESH_MODEL_KEY_COUNT];
+
+	/* Subscription List (group or virtual addresses) */
+	u16_t groups[CONFIG_BT_MESH_MODEL_GROUP_COUNT];
+
+	const struct bt_mesh_model_op * const op;
+
+	/* Model-specific user data */
+	void *user_data;
+};
+
+struct bt_mesh_send_cb {
+	void (*start)(u16_t duration, int err, void *cb_data);
+	void (*end)(int err, void *cb_data);
+};
+
+void bt_mesh_model_msg_init(struct os_mbuf *msg, u32_t opcode);
+
+/** Special TTL value to request using configured default TTL */
+#define BT_MESH_TTL_DEFAULT 0xff
+
+/** Maximum allowed TTL value */
+#define BT_MESH_TTL_MAX     0x7f
+
+/**
+ * @brief Send an Access Layer message.
+ *
+ * @param model     Mesh (client) Model that the message belongs to.
+ * @param ctx       Message context, includes keys, TTL, etc.
+ * @param msg       Access Layer payload (the actual message to be sent).
+ * @param cb        Optional "message sent" callback.
+ * @param cb_data   User data to be passed to the callback.
+ *
+ * @return 0 on success, or (negative) error code on failure.
+ */
+int bt_mesh_model_send(struct bt_mesh_model *model,
+		       struct bt_mesh_msg_ctx *ctx,
+		       struct os_mbuf *msg,
+		       const struct bt_mesh_send_cb *cb,
+		       void *cb_data);
+
+/**
+ * @brief Send a model publication message.
+ *
+ * Before calling this function, the user needs to ensure that the model
+ * publication message (@ref bt_mesh_model_pub.msg) contains a valid
+ * message to be sent. Note that this API is only to be used for
+ * non-period publishing. For periodic publishing the app only needs
+ * to make sure that @ref bt_mesh_model_pub.msg contains a valid message
+ * whenever the @ref bt_mesh_model_pub.update callback is called.
+ *
+ * @param model  Mesh (client) Model that's publishing the message.
+ *
+ * @return 0 on success, or (negative) error code on failure.
+ */
+int bt_mesh_model_publish(struct bt_mesh_model *model);
+
+/** Node Composition */
+struct bt_mesh_comp {
+	u16_t cid;
+	u16_t pid;
+	u16_t vid;
+
+	size_t elem_count;
+	struct bt_mesh_elem *elem;
+};
+
+/**
+ * @}
+ */
+
+#endif /* __BT_MESH_ACCESS_H */
diff --git a/net/nimble/host/mesh/include/mesh/cfg_cli.h b/net/nimble/host/mesh/include/mesh/cfg_cli.h
new file mode 100644
index 000000000..e3e7722ab
--- /dev/null
+++ b/net/nimble/host/mesh/include/mesh/cfg_cli.h
@@ -0,0 +1,184 @@
+/** @file
+ *  @brief Bluetooth Mesh Configuration Client Model APIs.
+ */
+
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef __BT_MESH_CFG_CLI_H
+#define __BT_MESH_CFG_CLI_H
+
+/**
+ * @brief Bluetooth Mesh
+ * @defgroup bt_mesh_cfg_cli Bluetooth Mesh Configuration Client Model
+ * @ingroup bt_mesh
+ * @{
+ */
+
+/** Mesh Configuration Client Model Context */
+struct bt_mesh_cfg_cli {
+	struct bt_mesh_model *model;
+
+	struct k_sem          op_sync;
+	u32_t                 op_pending;
+	void                 *op_param;
+};
+
+extern const struct bt_mesh_model_op bt_mesh_cfg_cli_op[];
+
+#define BT_MESH_MODEL_CFG_CLI(cli_data)                                      \
+		BT_MESH_MODEL(BT_MESH_MODEL_ID_CFG_CLI,                      \
+			      bt_mesh_cfg_cli_op, NULL, cli_data)
+
+int bt_mesh_cfg_comp_data_get(u16_t net_idx, u16_t addr, u8_t page,
+			      u8_t *status, struct os_mbuf *comp);
+
+int bt_mesh_cfg_beacon_get(u16_t net_idx, u16_t addr, u8_t *status);
+
+int bt_mesh_cfg_beacon_set(u16_t net_idx, u16_t addr, u8_t val, u8_t *status);
+
+int bt_mesh_cfg_ttl_get(u16_t net_idx, u16_t addr, u8_t *ttl);
+
+int bt_mesh_cfg_ttl_set(u16_t net_idx, u16_t addr, u8_t val, u8_t *ttl);
+
+int bt_mesh_cfg_friend_get(u16_t net_idx, u16_t addr, u8_t *status);
+
+int bt_mesh_cfg_friend_set(u16_t net_idx, u16_t addr, u8_t val, u8_t *status);
+
+int bt_mesh_cfg_gatt_proxy_get(u16_t net_idx, u16_t addr, u8_t *status);
+
+int bt_mesh_cfg_gatt_proxy_set(u16_t net_idx, u16_t addr, u8_t val,
+			       u8_t *status);
+
+int bt_mesh_cfg_relay_get(u16_t net_idx, u16_t addr, u8_t *status,
+			  u8_t *transmit);
+
+int bt_mesh_cfg_relay_set(u16_t net_idx, u16_t addr, u8_t new_relay,
+			  u8_t new_transmit, u8_t *status, u8_t *transmit);
+
+int bt_mesh_cfg_net_key_add(u16_t net_idx, u16_t addr, u16_t key_net_idx,
+			    const u8_t net_key[16], u8_t *status);
+
+int bt_mesh_cfg_app_key_add(u16_t net_idx, u16_t addr, u16_t key_net_idx,
+			    u16_t key_app_idx, const u8_t app_key[16],
+			    u8_t *status);
+
+int bt_mesh_cfg_mod_app_bind(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			     u16_t mod_app_idx, u16_t mod_id, u8_t *status);
+
+int bt_mesh_cfg_mod_app_bind_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				 u16_t mod_app_idx, u16_t mod_id, u16_t cid,
+				 u8_t *status);
+
+struct bt_mesh_cfg_mod_pub {
+	u16_t  addr;
+	u16_t  app_idx;
+	bool   cred_flag;
+	u8_t   ttl;
+	u8_t   period;
+	u8_t   transmit;
+};
+
+int bt_mesh_cfg_mod_pub_get(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			    u16_t mod_id, struct bt_mesh_cfg_mod_pub *pub,
+			    u8_t *status);
+
+int bt_mesh_cfg_mod_pub_get_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				u16_t mod_id, u16_t cid,
+				struct bt_mesh_cfg_mod_pub *pub, u8_t *status);
+
+int bt_mesh_cfg_mod_pub_set(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			    u16_t mod_id, struct bt_mesh_cfg_mod_pub *pub,
+			    u8_t *status);
+
+int bt_mesh_cfg_mod_pub_set_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				u16_t mod_id, u16_t cid,
+				struct bt_mesh_cfg_mod_pub *pub, u8_t *status);
+
+int bt_mesh_cfg_mod_sub_add(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			    u16_t sub_addr, u16_t mod_id, u8_t *status);
+
+int bt_mesh_cfg_mod_sub_add_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				 u16_t sub_addr, u16_t mod_id, u16_t cid,
+				 u8_t *status);
+
+int bt_mesh_cfg_mod_sub_del(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			    u16_t sub_addr, u16_t mod_id, u8_t *status);
+
+int bt_mesh_cfg_mod_sub_del_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				 u16_t sub_addr, u16_t mod_id, u16_t cid,
+				 u8_t *status);
+
+int bt_mesh_cfg_mod_sub_overwrite(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				  u16_t sub_addr, u16_t mod_id, u8_t *status);
+
+int bt_mesh_cfg_mod_sub_overwrite_vnd(u16_t net_idx, u16_t addr,
+				      u16_t elem_addr, u16_t sub_addr,
+				      u16_t mod_id, u16_t cid, u8_t *status);
+
+int bt_mesh_cfg_mod_sub_va_add(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			       const u8_t label[16], u16_t mod_id,
+			       u16_t *virt_addr, u8_t *status);
+
+int bt_mesh_cfg_mod_sub_va_add_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				   const u8_t label[16], u16_t mod_id,
+				   u16_t cid, u16_t *virt_addr, u8_t *status);
+
+int bt_mesh_cfg_mod_sub_va_del(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			       const u8_t label[16], u16_t mod_id,
+			       u16_t *virt_addr, u8_t *status);
+
+int bt_mesh_cfg_mod_sub_va_del_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				   const u8_t label[16], u16_t mod_id,
+				   u16_t cid, u16_t *virt_addr, u8_t *status);
+
+int bt_mesh_cfg_mod_sub_va_overwrite(u16_t net_idx, u16_t addr,
+				     u16_t elem_addr, const u8_t label[16],
+				     u16_t mod_id, u16_t *virt_addr,
+				     u8_t *status);
+
+int bt_mesh_cfg_mod_sub_va_overwrite_vnd(u16_t net_idx, u16_t addr,
+					 u16_t elem_addr, const u8_t label[16],
+					 u16_t mod_id, u16_t cid,
+					 u16_t *virt_addr, u8_t *status);
+
+struct bt_mesh_cfg_hb_sub {
+	u16_t src;
+	u16_t dst;
+	u8_t  period;
+	u8_t  count;
+	u8_t  min;
+	u8_t  max;
+};
+
+int bt_mesh_cfg_hb_sub_set(u16_t net_idx, u16_t addr,
+			   struct bt_mesh_cfg_hb_sub *sub, u8_t *status);
+
+int bt_mesh_cfg_hb_sub_get(u16_t net_idx, u16_t addr,
+			   struct bt_mesh_cfg_hb_sub *sub, u8_t *status);
+
+struct bt_mesh_cfg_hb_pub {
+	u16_t dst;
+	u8_t  count;
+	u8_t  period;
+	u8_t  ttl;
+	u16_t feat;
+	u16_t net_idx;
+};
+
+int bt_mesh_cfg_hb_pub_set(u16_t net_idx, u16_t addr,
+			   const struct bt_mesh_cfg_hb_pub *pub, u8_t *status);
+
+int bt_mesh_cfg_hb_pub_get(u16_t net_idx, u16_t addr,
+			   struct bt_mesh_cfg_hb_pub *pub, u8_t *status);
+
+s32_t bt_mesh_cfg_cli_timeout_get(void);
+void bt_mesh_cfg_cli_timeout_set(s32_t timeout);
+
+/**
+ * @}
+ */
+
+#endif /* __BT_MESH_CFG_CLI_H */
diff --git a/net/nimble/host/mesh/include/mesh/cfg_srv.h b/net/nimble/host/mesh/include/mesh/cfg_srv.h
new file mode 100644
index 000000000..387480f38
--- /dev/null
+++ b/net/nimble/host/mesh/include/mesh/cfg_srv.h
@@ -0,0 +1,69 @@
+/** @file
+ *  @brief Bluetooth Mesh Configuration Server Model APIs.
+ */
+
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef __BT_MESH_CFG_SRV_H
+#define __BT_MESH_CFG_SRV_H
+
+/**
+ * @brief Bluetooth Mesh
+ * @defgroup bt_mesh_cfg_srv Bluetooth Mesh Configuration Server Model
+ * @ingroup bt_mesh
+ * @{
+ */
+
+/** Mesh Configuration Server Model Context */
+struct bt_mesh_cfg_srv {
+	struct bt_mesh_model *model;
+
+	u8_t net_transmit;         /* Network Transmit state */
+	u8_t relay;                /* Relay Mode state */
+	u8_t relay_retransmit;     /* Relay Retransmit state */
+	u8_t beacon;               /* Secure Network Beacon state */
+	u8_t gatt_proxy;           /* GATT Proxy state */
+	u8_t frnd;                 /* Friend state */
+	u8_t default_ttl;          /* Default TTL */
+
+	/* Heartbeat Publication */
+	struct {
+		struct k_delayed_work timer;
+
+		u16_t dst;
+		u16_t count;
+		u8_t  period;
+		u8_t  ttl;
+		u16_t feat;
+		u16_t net_idx;
+	} hb_pub;
+
+	/* Heartbeat Subscription */
+	struct {
+		s64_t  expiry;
+
+		u16_t src;
+		u16_t dst;
+		u16_t count;
+		u8_t  min_hops;
+		u8_t  max_hops;
+
+		/* Optional subscription tracking function */
+		void (*func)(u8_t hops, u16_t feat);
+	} hb_sub;
+};
+
+extern const struct bt_mesh_model_op bt_mesh_cfg_srv_op[];
+
+#define BT_MESH_MODEL_CFG_SRV(srv_data)                                      \
+		BT_MESH_MODEL(BT_MESH_MODEL_ID_CFG_SRV,                      \
+			      bt_mesh_cfg_srv_op, NULL, srv_data)
+
+/**
+ * @}
+ */
+
+#endif /* __BT_MESH_CFG_SRV_H */
diff --git a/net/nimble/host/mesh/include/mesh/glue.h b/net/nimble/host/mesh/include/mesh/glue.h
index ec7febcb1..d61a5169a 100644
--- a/net/nimble/host/mesh/include/mesh/glue.h
+++ b/net/nimble/host/mesh/include/mesh/glue.h
@@ -21,10 +21,10 @@
 #define _MESH_GLUE_
 
 #include <assert.h>
+#include <errno.h>
 
 #include "syscfg/syscfg.h"
 
-#include "os/os_mbuf.h"
 #include "os/os_mbuf.h"
 #include "os/os_callout.h"
 #include "os/os_eventq.h"
@@ -126,6 +126,15 @@
         assert(code);             \
     } while (0);
 
+#define __ASSERT_NO_MSG(test) __ASSERT(test, "")
+
+/* Mesh is designed to not use mbuf chains */
+#if BT_DBG_ENABLED
+#define ASSERT_NOT_CHAIN(om) assert(SLIST_NEXT(om, om_next) == NULL)
+#else
+#define ASSERT_NOT_CHAIN(om) (void)(om)
+#endif
+
 #define __packed    __attribute__((__packed__))
 
 #define MSEC_PER_SEC   (1000)
@@ -134,6 +143,12 @@
 #define K_MINUTES(m)   K_SECONDS((m) * 60)
 #define K_HOURS(h)     K_MINUTES((h) * 60)
 
+#ifndef BIT
+#define BIT(n)  (1UL << (n))
+#endif
+
+#define BIT_MASK(n) (BIT(n) - 1)
+
 #define BT_GAP_ADV_FAST_INT_MIN_1               0x0030  /* 30 ms    */
 #define BT_GAP_ADV_FAST_INT_MAX_1               0x0060  /* 60 ms    */
 #define BT_GAP_ADV_FAST_INT_MIN_2               0x00a0  /* 100 ms   */
@@ -317,4 +332,66 @@ 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_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
+#define CONFIG_BT_MESH_PB_ADV               BLE_MESH_PB_ADV
+#define CONFIG_BT_MESH_PB_GATT              BLE_MESH_PB_GATT
+#define CONFIG_BT_MESH_PROV                 BLE_MESH_PROV
+
+/* Above flags are used with IS_ENABLED macro */
+#define IS_ENABLED(config) MYNEWT_VAL(config)
+
+#define CONFIG_BLUETOOTH_MESH_LPN_GROUPS    MYNEWT_VAL(BLE_MESH_LPN_GROUPS)
+#define CONFIG_BT_MESH_ADV_BUF_COUNT        MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT)
+#define CONFIG_BT_MESH_FRIEND_QUEUE_SIZE    MYNEWT_VAL(BLE_MESH_FRIEND_QUEUE_SIZE)
+#define CONFIG_BT_MESH_FRIEND_RECV_WIN      MYNEWT_VAL(BLE_MESH_FRIEND_RECV_WIN)
+#define CONFIG_BT_MESH_LPN_POLL_TIMEOUT     MYNEWT_VAL(BLE_MESH_LPN_POLL_TIMEOUT)
+#define CONFIG_BT_MESH_MODEL_GROUP_COUNT    MYNEWT_VAL(BLE_MESH_MODEL_GROUP_COUNT)
+#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 printk console_printf
+
+#define CONTAINER_OF(ptr, type, field) \
+	((type *)(((char *)(ptr)) - offsetof(type, field)))
+
+
+#define k_sem os_sem
+
+static inline void k_sem_init(struct k_sem *sem, unsigned int initial_count,
+			      unsigned int limit)
+{
+	os_sem_init(sem, initial_count);
+}
+
+static inline int k_sem_take(struct k_sem *sem, s32_t timeout)
+{
+	return - os_sem_pend(sem, timeout);
+}
+
+static inline void k_sem_give(struct k_sem *sem)
+{
+	os_sem_release(sem);
+}
+
+/* Helpers to access the storage array, since we don't have access to its
+ * type at this point anymore.
+ */
+
+#define BUF_SIZE(pool) (pool->omp_pool->mp_block_size)
+
+static inline int net_buf_id(struct os_mbuf *buf)
+{
+	struct os_mbuf_pool *pool = buf->om_omp;
+	u8_t *pool_start = (u8_t *)pool->omp_pool->mp_membuf_addr;
+	u8_t *buf_ptr = (u8_t *)buf;
+
+	return (buf_ptr - pool_start) / BUF_SIZE(pool);
+}
+
 #endif
diff --git a/net/nimble/host/mesh/include/mesh/health_cli.h b/net/nimble/host/mesh/include/mesh/health_cli.h
new file mode 100644
index 000000000..8b890422e
--- /dev/null
+++ b/net/nimble/host/mesh/include/mesh/health_cli.h
@@ -0,0 +1,72 @@
+/** @file
+ *  @brief Bluetooth Mesh Health Client Model APIs.
+ */
+
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef __BT_MESH_HEALTH_CLI_H
+#define __BT_MESH_HEALTH_CLI_H
+
+/**
+ * @brief Bluetooth Mesh
+ * @defgroup bt_mesh_health_cli Bluetooth Mesh Health Client Model
+ * @ingroup bt_mesh
+ * @{
+ */
+
+/** Mesh Health Client Model Context */
+struct bt_mesh_health_cli {
+	struct bt_mesh_model *model;
+
+	void (*current_status)(struct bt_mesh_health_cli *cli, u16_t addr,
+			       u8_t test_id, u16_t cid, u8_t *faults,
+			       size_t fault_count);
+
+	struct k_sem          op_sync;
+	u32_t                 op_pending;
+	void                 *op_param;
+};
+
+extern const struct bt_mesh_model_op bt_mesh_health_cli_op[];
+
+#define BT_MESH_MODEL_HEALTH_CLI(cli_data)                                   \
+		BT_MESH_MODEL(BT_MESH_MODEL_ID_HEALTH_CLI,                   \
+			      bt_mesh_health_cli_op, NULL, cli_data)
+
+int bt_mesh_health_cli_set(struct bt_mesh_model *model);
+
+int bt_mesh_health_fault_get(u16_t net_idx, u16_t addr, u16_t app_idx,
+			     u16_t cid, u8_t *test_id, u8_t *faults,
+			     size_t *fault_count);
+
+int bt_mesh_health_fault_clear(u16_t net_idx, u16_t addr, u16_t app_idx,
+			       u16_t cid, u8_t *test_id, u8_t *faults,
+			       size_t *fault_count);
+
+int bt_mesh_health_fault_test(u16_t net_idx, u16_t addr, u16_t app_idx,
+			      u16_t cid, u8_t test_id, u8_t *faults,
+			      size_t *fault_count);
+
+int bt_mesh_health_period_get(u16_t net_idx, u16_t addr, u16_t app_idx,
+			      u8_t *divisor);
+
+int bt_mesh_health_period_set(u16_t net_idx, u16_t addr, u16_t app_idx,
+			      u8_t divisor, u8_t *updated_divisor);
+
+int bt_mesh_health_attention_get(u16_t net_idx, u16_t addr, u16_t app_idx,
+				 u8_t *attention);
+
+int bt_mesh_health_attention_set(u16_t net_idx, u16_t addr, u16_t app_idx,
+				 u8_t attention, u8_t *updated_attention);
+
+s32_t bt_mesh_health_cli_timeout_get(void);
+void bt_mesh_health_cli_timeout_set(s32_t timeout);
+
+/**
+ * @}
+ */
+
+#endif /* __BT_MESH_HEALTH_CLI_H */
diff --git a/net/nimble/host/mesh/include/mesh/health_srv.h b/net/nimble/host/mesh/include/mesh/health_srv.h
new file mode 100644
index 000000000..96f69e1e0
--- /dev/null
+++ b/net/nimble/host/mesh/include/mesh/health_srv.h
@@ -0,0 +1,91 @@
+/** @file
+ *  @brief Bluetooth Mesh Health Server Model APIs.
+ */
+
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef __BT_MESH_HEALTH_SRV_H
+#define __BT_MESH_HEALTH_SRV_H
+
+/**
+ * @brief Mesh Bluetooth Mesh Health Server Model
+ * @defgroup bt_mesh_health_srv
+ * @ingroup bt_mesh
+ * @{
+ */
+
+struct bt_mesh_health_srv_cb {
+	/* Fetch current faults */
+	int (*fault_get_cur)(struct bt_mesh_model *model, u8_t *test_id,
+			     u16_t *company_id, u8_t *faults,
+			     u8_t *fault_count);
+
+	/* Fetch registered faults */
+	int (*fault_get_reg)(struct bt_mesh_model *model, u16_t company_id,
+			     u8_t *test_id, u8_t *faults,
+			     u8_t *fault_count);
+
+	/* Clear registered faults */
+	int (*fault_clear)(struct bt_mesh_model *model, u16_t company_id);
+
+	/* Run a specific test */
+	int (*fault_test)(struct bt_mesh_model *model, u8_t test_id,
+			  u16_t company_id);
+
+	/* Attention on */
+	void (*attn_on)(struct bt_mesh_model *model);
+
+	/* Attention off */
+	void (*attn_off)(struct bt_mesh_model *model);
+};
+
+/** @def BT_MESH_HEALTH_FAULT_MSG
+ *
+ *  A helper to define a health fault message.
+ *
+ *  @param max_faults Maximum number of faults the element can have.
+ *
+ *  @return a New net_buf_simple of the needed size.
+ */
+#define BT_MESH_HEALTH_FAULT_MSG(max_faults) \
+	NET_BUF_SIMPLE(1 + 3 + (max_faults))
+
+/** Mesh Health Server Model Context */
+struct bt_mesh_health_srv {
+	struct bt_mesh_model *model;
+
+	/* Optional callback struct */
+	const struct bt_mesh_health_srv_cb *cb;
+
+	/* Attention Timer state */
+	struct k_delayed_work attn_timer;
+};
+
+int bt_mesh_fault_update(struct bt_mesh_elem *elem);
+
+extern const struct bt_mesh_model_op bt_mesh_health_srv_op[];
+
+/** @def BT_MESH_MODEL_HEALTH_SRV
+ *
+ *  Define a new health server model. Note that this API needs to be
+ *  repeated for each element that the application wants to have a
+ *  health server model on. Each instance also needs a unique
+ *  bt_mesh_health_srv and bt_mesh_model_pub context.
+ *
+ *  @param srv Pointer to a unique struct bt_mesh_health_srv.
+ *  @param pub Pointer to a unique struct bt_mesh_model_pub.
+ *
+ *  @return New mesh model instance.
+ */
+#define BT_MESH_MODEL_HEALTH_SRV(srv, pub)                                   \
+		BT_MESH_MODEL(BT_MESH_MODEL_ID_HEALTH_SRV,                   \
+			      bt_mesh_health_srv_op, pub, srv)
+
+/**
+ * @}
+ */
+
+#endif /* __BT_MESH_HEALTH_SRV_H */
diff --git a/net/nimble/host/mesh/include/mesh/main.h b/net/nimble/host/mesh/include/mesh/main.h
new file mode 100644
index 000000000..4b7c5fd65
--- /dev/null
+++ b/net/nimble/host/mesh/include/mesh/main.h
@@ -0,0 +1,325 @@
+/** @file
+ *  @brief Bluetooth Mesh Profile APIs.
+ */
+
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef __BT_MESH_MAIN_H
+#define __BT_MESH_MAIN_H
+
+/**
+ * @brief Bluetooth Mesh Provisioning
+ * @defgroup bt_mesh_prov Bluetooth Mesh Provisioning
+ * @ingroup bt_mesh
+ * @{
+ */
+
+typedef enum {
+	BT_MESH_NO_OUTPUT       = 0,
+	BT_MESH_BLINK           = BIT(0),
+	BT_MESH_BEEP            = BIT(1),
+	BT_MESH_VIBRATE         = BIT(2),
+	BT_MESH_DISPLAY_NUMBER  = BIT(3),
+	BT_MESH_DISPLAY_STRING  = BIT(4),
+} bt_mesh_output_action_t;
+
+typedef enum {
+	BT_MESH_NO_INPUT      = 0,
+	BT_MESH_PUSH          = BIT(0),
+	BT_MESH_TWIST         = BIT(1),
+	BT_MESH_ENTER_NUMBER  = BIT(2),
+	BT_MESH_ENTER_STRING  = BIT(3),
+} bt_mesh_input_action_t;
+
+typedef enum {
+	BT_MESH_PROV_ADV   = BIT(0),
+	BT_MESH_PROV_GATT  = BIT(1),
+} bt_mesh_prov_bearer_t;
+
+/** Provisioning properties & capabilities. */
+struct bt_mesh_prov {
+	/** The UUID that's used when advertising as unprovisioned */
+	const u8_t *uuid;
+
+	/** Static OOB value */
+	const u8_t *static_val;
+	/** Static OOB value length */
+	u8_t        static_val_len;
+
+	/** Maximum size of Output OOB supported */
+	u8_t        output_size;
+	/** Supported Output OOB Actions */
+	u16_t       output_actions;
+
+	/* Maximum size of Input OOB supported */
+	u8_t        input_size;
+	/** Supported Input OOB Actions */
+	u16_t       input_actions;
+
+	/** @brief Output of a number is requested.
+	 *
+	 *  This callback notifies the application that it should
+	 *  output the given number using the given action.
+	 *
+	 *  @param act Action for outputting the number.
+	 *  @param num Number to be outputted.
+	 *
+	 *  @return Zero on success or negative error code otherwise
+	 */
+	int         (*output_number)(bt_mesh_output_action_t act, u32_t num);
+
+	/** @brief Output of a string is requested.
+	 *
+	 *  This callback notifies the application that it should
+	 *  display the given string to the user.
+	 *
+	 *  @param str String to be displayed.
+	 *
+	 *  @return Zero on success or negative error code otherwise
+	 */
+	int         (*output_string)(const char *str);
+
+	/** @brief Input is requested.
+	 *
+	 *  This callback notifies the application that it should
+	 *  request input from the user using the given action. The
+	 *  requested input will either be a string or a number, and
+	 *  the application needs to consequently call the
+	 *  bt_mesh_input_string() or bt_mesh_input_number() functions
+	 *  once the data has been acquired from the user.
+	 *
+	 *  @param act Action for inputting data.
+	 *  @param num Maximum size of the inputted data.
+	 *
+	 *  @return Zero on success or negative error code otherwise
+	 */
+	int         (*input)(bt_mesh_input_action_t act, u8_t size);
+
+	/** @brief Provisioning link has been opened.
+	 *
+	 *  This callback notifies the application that a provisioning
+	 *  link has been opened on the given provisioning bearer.
+	 *
+	 *  @param bearer Provisioning bearer.
+	 */
+	void        (*link_open)(bt_mesh_prov_bearer_t bearer);
+
+	/** @brief Provisioning link has been closed.
+	 *
+	 *  This callback notifies the application that a provisioning
+	 *  link has been closed on the given provisioning bearer.
+	 *
+	 *  @param bearer Provisioning bearer.
+	 */
+	void        (*link_close)(bt_mesh_prov_bearer_t bearer);
+
+	/** @brief Provisioning is complete.
+	 *
+	 *  This callback notifies the application that provisioning has
+	 *  been successfully completed, and that the local node has been
+	 *  assigned the specified NetKeyIndex and primary element address.
+	 *
+	 *  @param net_idx NetKeyIndex given during provisioning.
+	 *  @param addr Primary element address.
+	 */
+	void        (*complete)(u16_t net_idx, u16_t addr);
+
+	/** @brief Node has been reset.
+	 *
+	 *  This callback notifies the application that the local node
+	 *  has been reset and needs to be reprovisioned. The node will
+	 *  not automatically advertise as unprovisioned, rather the
+	 *  bt_mesh_prov_enable() API needs to be called to enable
+	 *  unprovisioned advertising on one or more provisioning bearers.
+	 */
+	void        (*reset)(void);
+};
+
+/** @brief Provide provisioning input OOB string.
+ *
+ *  This is intended to be called after the bt_mesh_prov input callback
+ *  has been called with BT_MESH_ENTER_STRING as the action.
+ *
+ *  @param str String.
+ *
+ *  @return Zero on success or (negative) error code otherwise.
+ */
+int bt_mesh_input_string(const char *str);
+
+/** @brief Provide provisioning input OOB number.
+ *
+ *  This is intended to be called after the bt_mesh_prov input callback
+ *  has been called with BT_MESH_ENTER_NUMBER as the action.
+ *
+ *  @param num Number.
+ *
+ *  @return Zero on success or (negative) error code otherwise.
+ */
+int bt_mesh_input_number(u32_t num);
+
+/** @brief Enable specific provisioning bearers
+ *
+ *  Enable one or more provisioning bearers.
+ *
+ *  @param bearers Bit-wise or of provisioning bearers.
+ *
+ *  @return Zero on success or (negative) error code otherwise.
+ */
+int bt_mesh_prov_enable(bt_mesh_prov_bearer_t bearers);
+
+/** @brief Disable specific provisioning bearers
+ *
+ *  Disable one or more provisioning bearers.
+ *
+ *  @param bearers Bit-wise or of provisioning bearers.
+ *
+ *  @return Zero on success or (negative) error code otherwise.
+ */
+int bt_mesh_prov_disable(bt_mesh_prov_bearer_t bearers);
+
+/**
+ * @}
+ */
+
+/**
+ * @brief Bluetooth Mesh
+ * @defgroup bt_mesh Bluetooth Mesh
+ * @ingroup bluetooth
+ * @{
+ */
+
+/* Primary Network Key index */
+#define BT_MESH_NET_PRIMARY                 0x000
+
+#define BT_MESH_RELAY_DISABLED              0x00
+#define BT_MESH_RELAY_ENABLED               0x01
+#define BT_MESH_RELAY_NOT_SUPPORTED         0x02
+
+#define BT_MESH_BEACON_DISABLED             0x00
+#define BT_MESH_BEACON_ENABLED              0x01
+
+#define BT_MESH_GATT_PROXY_DISABLED         0x00
+#define BT_MESH_GATT_PROXY_ENABLED          0x01
+#define BT_MESH_GATT_PROXY_NOT_SUPPORTED    0x02
+
+#define BT_MESH_FRIEND_DISABLED             0x00
+#define BT_MESH_FRIEND_ENABLED              0x01
+#define BT_MESH_FRIEND_NOT_SUPPORTED        0x02
+
+#define BT_MESH_NODE_IDENTITY_STOPPED       0x00
+#define BT_MESH_NODE_IDENTITY_RUNNING       0x01
+#define BT_MESH_NODE_IDENTITY_NOT_SUPPORTED 0x02
+
+/* Features */
+#define BT_MESH_FEAT_RELAY                  BIT(0)
+#define BT_MESH_FEAT_PROXY                  BIT(1)
+#define BT_MESH_FEAT_FRIEND                 BIT(2)
+#define BT_MESH_FEAT_LOW_POWER              BIT(3)
+
+/** @brief Initialize Mesh support
+ *
+ *  After calling this API, the node will not automatically advertise as
+ *  unprovisioned, rather the bt_mesh_prov_enable() API needs to be called
+ *  to enable unprovisioned advertising on one or more provisioning bearers.
+ *
+ *  @param own_addr_type Node address type
+ *  @param prov Node provisioning information.
+ *  @param comp Node Composition.
+ *
+ *  @return Zero on success or (negative) error code otherwise.
+ */
+int bt_mesh_init(u8_t own_addr_type,
+		 const struct bt_mesh_prov *prov,
+		 const struct bt_mesh_comp *comp);
+
+/** @brief Reset the state of the local Mesh node.
+ *
+ *  Resets the state of the node, which means that it needs to be
+ *  reprovisioned to become an active node in a Mesh network again.
+ *
+ *  After calling this API, the node will not automatically advertise as
+ *  unprovisioned, rather the bt_mesh_prov_enable() API needs to be called
+ *  to enable unprovisioned advertising on one or more provisioning bearers.
+ *
+ */
+void bt_mesh_reset(void);
+
+/** @brief Provision the local Mesh Node.
+ *
+ *  This API should normally not be used directly by the application. The
+ *  only exception is for testing purposes where manual provisioning is
+ *  desired without an actual external provisioner.
+ *
+ *  @param net_key  Network Key
+ *  @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]);
+
+/** @brief Toggle the IV Update test mode
+ *
+ *  This API is only available if the IV Update test mode has been enabled
+ *  in Kconfig. It is needed for passing most of the IV Update qualification
+ *  test cases.
+ *
+ *  @param enable true to enable IV Update test mode, false to disable it.
+ */
+void bt_mesh_iv_update_test(bool enable);
+
+/** @brief Toggle the IV Update state
+ *
+ *  This API is only available if the IV Update test mode has been enabled
+ *  in Kconfig. It is needed for passing most of the IV Update qualification
+ *  test cases.
+ *
+ *  @return true if IV Update In Progress state was entered, false otherwise.
+ */
+bool bt_mesh_iv_update(void);
+
+/** @brief Toggle the Low Power feature of the local device
+ *
+ *  Enables or disables the Low Power feature of the local device. This is
+ *  exposed as a run-time feature, since the device might want to change
+ *  this e.g. based on being plugged into a stable power source or running
+ *  from a battery power source.
+ *
+ *  @param enable  true to enable LPN functionality, false to disable it.
+ *
+ *  @return Zero on success or (negative) error code otherwise.
+ */
+int bt_mesh_lpn_set(bool enable);
+
+/** @brief Send out a Friend Poll message.
+ *
+ *  Send a Friend Poll message to the Friend of this node. If there is no
+ *  established Friendship the function will return an error.
+ *
+ *  @return Zero on success or (negative) error code otherwise.
+ */
+int bt_mesh_lpn_poll(void);
+
+/** @brief Register a callback for Friendship changes.
+ *
+ *  Registers a callback that will be called whenever Friendship gets
+ *  established or is lost.
+ *
+ *  @param cb Function to call when the Friendship status changes.
+ */
+void bt_mesh_lpn_set_cb(void (*cb)(u16_t friend_addr, bool established));
+
+/**
+ * @}
+ */
+
+#endif /* __BT_MESH_MAIN_H */
diff --git a/net/nimble/host/mesh/include/mesh/mesh.h b/net/nimble/host/mesh/include/mesh/mesh.h
index dc27a9b10..31750d0c3 100644
--- a/net/nimble/host/mesh/include/mesh/mesh.h
+++ b/net/nimble/host/mesh/include/mesh/mesh.h
@@ -11,611 +11,25 @@
 #define __BT_MESH_H
 
 #include <stddef.h>
-#include <stdint.h>
-#include <errno.h>
-#include <string.h>
-
-#include "mesh/glue.h"
-
-
-/**
- * Print out all the incoming advertising packets
- * Used on log level 0
- */
-#define BT_MESH_EXTENDED_DEBUG 0
-
-/**
- *  @brief Parsing state of a buffer.
- *
- *  This is used for temporarily storing the parsing state of a buffer
- *  while giving control of the parsing to a routine which we don't
- *  control.
- */
-
-#define BIT(x) (1U << (x))
-#define BIT_MASK(n) (BIT(n) - 1)
-
-/**
- * @brief Bluetooth Mesh Proxy
- * @defgroup bt_mesh_proxy Bluetooth Mesh Proxy
- * @ingroup bt_mesh
- * @{
- */
-
-/**
- * @brief Enable advertising with Node Identity.
- *
- * This API requires that GATT Proxy support has been enabled. Once called
- * each subnet will start advertising using Node Identity for the next
- * 60 seconds.
- *
- * @return 0 on success, or (negative) error code on failure.
- */
-int
-bt_mesh_proxy_identity_enable(void);
-
-/**
- * @}
- */
-
-/**
- * @brief Bluetooth Mesh Composition
- * @defgroup bt_comp Bluetooth Mesh Composition
- * @ingroup bt_mesh
- * @{
- */
-
-#define BT_MESH_ADDR_UNASSIGNED   0x0000
-#define BT_MESH_ADDR_ALL_NODES    0xffff
-#define BT_MESH_ADDR_PROXIES      0xfffc
-#define BT_MESH_ADDR_FRIENDS      0xfffd
-#define BT_MESH_ADDR_RELAYS       0xfffe
-
-#define BT_MESH_KEY_UNUSED        0xffff
-#define BT_MESH_KEY_DEV           0xfffe
-
-/**
- * @brief Bluetooth Mesh Element
- * @defgroup bt_mesh_elem Bluetooth Mesh Element
- * @ingroup bt_comp
- * @{
- */
-
-/** Helper to define a mesh element within an array.
- *
- *  @param _loc       Location Descriptor.
- *  @param _mods      Array of models
- *  @param _vnd_mods  Array of vendor models.
- */
-#define BT_MESH_ELEM(_loc, _mods, _vnd_mods)        \
-{                                                   \
-	.loc              = (_loc),                 \
-	.model_count      = ARRAY_SIZE(_mods),      \
-	.models           = (_mods),                \
-	.vnd_model_count  = ARRAY_SIZE(_vnd_mods),  \
-	.vnd_models       = (_vnd_mods),            \
-}
-
-/** Abstraction that describes a Mesh Element */
-struct bt_mesh_elem
-{
-    /* Unicast Address. Set at runtime during provisioning. */
-    u16_t addr;
-
-    /* Location Descriptor (GATT Bluetooth Namespace Descriptors) */
-    const u16_t loc;
-
-    const u8_t model_count;
-    const u8_t vnd_model_count;
-
-    struct bt_mesh_model * const models;
-    struct bt_mesh_model * const vnd_models;
-};
-
-/**
- * @}
- */
-
-/**
- * @brief Bluetooth Mesh Model
- * @defgroup bt_mesh_model Bluetooth Mesh Model
- * @ingroup bt_comp
- * @{
- */
-
-/* Foundation Models */
-#define BT_MESH_MODEL_ID_CFG_SRV                   0x0000
-#define BT_MESH_MODEL_ID_CFG_CLI                   0x0001
-#define BT_MESH_MODEL_ID_HEALTH_SRV                0x0002
-#define BT_MESH_MODEL_ID_HEALTH_CLI                0x0003
-
-/* Models from the Mesh Model Specification */
-#define BT_MESH_MODEL_ID_GEN_ONOFF_SRV             0x1000
-#define BT_MESH_MODEL_ID_GEN_ONOFF_CLI             0x1001
-#define BT_MESH_MODEL_ID_GEN_LEVEL_SRV             0x1002
-#define BT_MESH_MODEL_ID_GEN_LEVEL_CLI             0x1003
-#define BT_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV    0x1004
-#define BT_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI    0x1005
-#define BT_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV       0x1006
-#define BT_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV 0x1007
-#define BT_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI       0x1008
-#define BT_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV       0x1009
-#define BT_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV 0x100a
-#define BT_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI       0x100b
-#define BT_MESH_MODEL_ID_GEN_BATTERY_SRV           0x100c
-#define BT_MESH_MODEL_ID_GEN_BATTERY_CLI           0x100d
-#define BT_MESH_MODEL_ID_GEN_LOCATION_SRV          0x100e
-#define BT_MESH_MODEL_ID_GEN_LOCATION_SETUPSRV     0x100f
-#define BT_MESH_MODEL_ID_GEN_LOCATION_CLI          0x1010
-#define BT_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV        0x1011
-#define BT_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV 0x1012
-#define BT_MESH_MODEL_ID_GEN_USER_PROP_SRV         0x1013
-#define BT_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV       0x1014
-#define BT_MESH_MODEL_ID_GEN_PROP_CLI              0x1015
-#define BT_MESH_MODEL_ID_SENSOR_SRV                0x1100
-#define BT_MESH_MODEL_ID_SENSOR_SETUP_SRV          0x1101
-#define BT_MESH_MODEL_ID_SENSOR_CLI                0x1102
-#define BT_MESH_MODEL_ID_TIME_SRV                  0x1200
-#define BT_MESH_MODEL_ID_TIME_SETUP_SRV            0x1201
-#define BT_MESH_MODEL_ID_TIME_CLI                  0x1202
-#define BT_MESH_MODEL_ID_SCENE_SRV                 0x1203
-#define BT_MESH_MODEL_ID_SCENE_SETUP_SRV           0x1204
-#define BT_MESH_MODEL_ID_SCENE_CLI                 0x1205
-#define BT_MESH_MODEL_ID_SCHEDULER_SRV             0x1206
-#define BT_MESH_MODEL_ID_SCHEDULER_SETUP_SRV       0x1207
-#define BT_MESH_MODEL_ID_SCHEDULER_CLI             0x1208
-#define BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV       0x1300
-#define BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV 0x1301
-#define BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI       0x1302
-#define BT_MESH_MODEL_ID_LIGHT_CTL_SRV             0x1303
-#define BT_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV       0x1304
-#define BT_MESH_MODEL_ID_LIGHT_CTL_CLI             0x1305
-#define BT_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV        0x1306
-#define BT_MESH_MODEL_ID_LIGHT_HSL_SRV             0x1307
-#define BT_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV       0x1308
-#define BT_MESH_MODEL_ID_LIGHT_HSL_CLI             0x1309
-#define BT_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV         0x130a
-#define BT_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV         0x130b
-#define BT_MESH_MODEL_ID_LIGHT_XYL_SRV             0x130c
-#define BT_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV       0x130d
-#define BT_MESH_MODEL_ID_LIGHT_XYL_CLI             0x130e
-#define BT_MESH_MODEL_ID_LIGHT_LC_SRV              0x130f
-#define BT_MESH_MODEL_ID_LIGHT_LC_SETUPSRV         0x1310
-#define BT_MESH_MODEL_ID_LIGHT_LC_CLI              0x1311
-
-/** Message sending context. */
-struct bt_mesh_msg_ctx
-{
-    /** NetKey Index of the subnet to send the message on. */
-    u16_t net_idx;
-
-    /** AppKey Index to encrypt the message with. */
-    u16_t app_idx;
-
-    /** Remote address. */
-    u16_t addr;
-
-    /** Received TTL value. Not used for sending. */
-    u8_t recv_ttl :7;
-
-    /** Whether friend credentials are used. */
-    u8_t friend_cred :1;
-
-    /** TTL, or BT_MESH_TTL_DEFAULT for default TTL. */
-    u8_t send_ttl;
-};
-
-struct bt_mesh_model_op
-{
-    /* OpCode encoded using the BT_MESH_MODEL_OP_* macros */
-    const u32_t opcode;
-
-    /* Minimum required message length */
-    const size_t min_len;
-
-    /* Message handler for the opcode */
-    void
-    (* const func)(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
-                   struct os_mbuf *buf);
-};
-
-#define BT_MESH_MODEL_OP_1(b0) (b0)
-#define BT_MESH_MODEL_OP_2(b0, b1) (((b0) << 8) | (b1))
-#define BT_MESH_MODEL_OP_3(b0, cid) ((((b0) << 16) | 0xc00000) | (cid))
-
-#define BT_MESH_MODEL_OP_END { 0, 0, NULL }
-#define BT_MESH_MODEL_NO_OPS ((struct bt_mesh_model_op []) \
-			      { BT_MESH_MODEL_OP_END })
-
-/** Helper to define an empty model array */
-#define BT_MESH_MODEL_EMPTY ((struct bt_mesh_model []){})
-
-#define BT_MESH_MODEL(_id, _op, _pub, _user_data)                            \
-{                                                                            \
-	.id = (_id),                                                         \
-	.op = _op,                                                           \
-	.keys = { [0 ... (MYNEWT_VAL(BLE_MESH_MODEL_KEY_COUNT) - 1)] =      \
-			BT_MESH_KEY_UNUSED },                                \
-	.pub = _pub,                                                         \
-	.groups = { [0 ... (MYNEWT_VAL(BLE_MESH_MODEL_GROUP_COUNT) - 1)] =  \
-			BT_MESH_ADDR_UNASSIGNED },                           \
-	.user_data = _user_data,                                             \
-}
-
-#define BT_MESH_MODEL_VND(_company, _id, _op, _pub, _user_data)              \
-{                                                                            \
-	.vnd.company = (_company),                                           \
-	.vnd.id = (_id),                                                     \
-	.op = _op,                                                           \
-	.pub = _pub,                                                         \
-	.keys = { [0 ... (MYNEWT_VAL(BLE_MESH_MODEL_KEY_COUNT) - 1)] =      \
-			BT_MESH_KEY_UNUSED },                                \
-	.groups = { [0 ... (MYNEWT_VAL(BLE_MESH_MODEL_GROUP_COUNT) - 1)] =  \
-			BT_MESH_ADDR_UNASSIGNED },                           \
-	.user_data = _user_data,                                             \
-}
-
-/** Mesh Configuration Server Model Context */
-struct bt_mesh_cfg
-{
-    struct bt_mesh_model *model;
-
-    u8_t net_transmit; /* Network Transmit state */
-    u8_t relay; /* Relay Mode state */
-    u8_t relay_retransmit; /* Relay Retransmit state */
-    u8_t beacon; /* Secure Network Beacon state */
-    u8_t gatt_proxy; /* GATT Proxy state */
-    u8_t frnd; /* Friend state */
-    u8_t default_ttl; /* Default TTL */
-
-    /* Heartbeat Publication */
-    struct
-    {
-        struct k_delayed_work timer;
-
-        u16_t dst;u16_t count;u8_t period;u8_t ttl;u16_t feat;u16_t net_idx;
-    } hb_pub;
-
-    /* Heartbeat Subscription */
-    struct
-    {
-        s64_t expiry;
-
-        u16_t src;u16_t dst;u16_t count;u8_t min_hops;u8_t max_hops;
-
-        /* Optional subscription tracking function */
-        void
-        (*func)(u8_t hops, u16_t feat);
-    } hb_sub;
-};
-
-extern const struct bt_mesh_model_op bt_mesh_cfg_op[];
-
-#define BT_MESH_MODEL_CFG_SRV(srv_data)                                      \
-		BT_MESH_MODEL(BT_MESH_MODEL_ID_CFG_SRV,                      \
-			      bt_mesh_cfg_op, NULL, srv_data)
-
-/** @def BT_MESH_TRANSMIT
- *
- *  @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.
- *
- *  @return Mesh transmit value that can be used e.g. for the default
- *          values of the configuration model data.
- */
-#define BT_MESH_TRANSMIT(count, int_ms) ((count) | (((int_ms / 10) - 1) << 3))
-
-/** Mesh Health Server Model Context */
-struct bt_mesh_health
-{
-    struct bt_mesh_model *model;
-
-    /* Fetch current faults */
-    int
-    (*fault_get_cur)(struct bt_mesh_model *model, u8_t *test_id,
-    u16_t *company_id,
-                     u8_t *faults,
-                     u8_t *fault_count);
-
-    /* Fetch registered faults */
-    int
-    (*fault_get_reg)(struct bt_mesh_model *model, u16_t company_id,
-    u8_t *test_id,
-                     u8_t *faults,
-                     u8_t *fault_count);
-
-    /* Clear registered faults */
-    int
-    (*fault_clear)(struct bt_mesh_model *model, u16_t company_id);
-
-    /* Run a specific test */
-    int
-    (*fault_test)(struct bt_mesh_model *model, u8_t test_id,
-    u16_t company_id);
-
-    /* Attention Timer state */
-    struct
-    {
-        struct k_delayed_work timer;
-        void
-        (*on)(struct bt_mesh_model *model);
-        void
-        (*off)(struct bt_mesh_model *model);
-    } attention;
-};
-
-int
-bt_mesh_fault_update(struct bt_mesh_elem *elem);
-
-extern const struct bt_mesh_model_op bt_mesh_health_op[];
-extern struct bt_mesh_model_pub bt_mesh_health_pub;
-
-#define BT_MESH_MODEL_HEALTH_SRV(srv_data)                                   \
-		BT_MESH_MODEL(BT_MESH_MODEL_ID_HEALTH_SRV,                   \
-			      bt_mesh_health_op, &bt_mesh_health_pub,        \
-			      srv_data)
-
-struct bt_mesh_model_pub
-{
-    /* Self-reference for easy lookup */
-    struct bt_mesh_model *mod;
-
-    u16_t addr; /* Publish Address */
-    u16_t key; /* Publish AppKey Index */
-
-    u32_t ttl :8, /* Publish Time to Live */
-    retransmit :8, /* Retransmit Count & Interval Steps */
-    period :8, /* Publish Period */
-    period_div :4, /* Divisor for the Period */
-    cred :1; /* Friendship Credentials Flag */
-
-    /* Publish callback */
-    void
-    (*func)(struct bt_mesh_model *mod);
-
-    /* Publish Period Timer */
-    struct k_delayed_work timer;
-};
-
-/** Abstraction that describes a Mesh Model instance */
-struct bt_mesh_model
-{
-    union
-    {
-        const u16_t id;
-        struct
-        {
-            u16_t company;u16_t id;
-        } vnd;
-    };
-
-    /* The Element this Model belongs to */
-    struct bt_mesh_elem *elem;
-
-    /* Model Publication */
-    struct bt_mesh_model_pub * const pub;
-
-    /* AppKey List */
-    u16_t keys[MYNEWT_VAL(BLE_MESH_MODEL_KEY_COUNT)];
-
-    /* Subscription List (group or virtual addresses) */
-    u16_t groups[MYNEWT_VAL(BLE_MESH_MODEL_GROUP_COUNT)];
-
-    const struct bt_mesh_model_op * const op;
-
-    /* Model-specific user data */
-    void *user_data;
-};
-
-typedef void
-(*bt_mesh_cb_t)(int err, void *cb_data);
-
-void
-bt_mesh_model_msg_init(struct os_mbuf *msg, u32_t opcode);
-
-/** Special TTL value to request using configured default TTL */
-#define BT_MESH_TTL_DEFAULT 0xff
-
-/** Maximum allowed TTL value */
-#define BT_MESH_TTL_MAX     0x7f
-
-/**
- * @brief Send an Access Layer message.
- *
- * @param model     Mesh (client) Model that the message belongs to.
- * @param ctx       Message context, includes keys, TTL, etc.
- * @param msg       Access Layer payload (the actual message to be sent).
- * @param cb        Optional "message sent" callback.
- * @param cb_data   User data to be passed to the callback.
- *
- * @return 0 on success, or (negative) error code on failure.
- */
-int
-bt_mesh_model_send(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
-                   struct os_mbuf *msg, bt_mesh_cb_t cb, void *cb_data);
-
-/**
- * @brief Send a model publication message.
- *
- * @param model  Mesh (client) Model that's publishing the message.
- * @param msg    Access Layer message to publish.
- *
- * @return 0 on success, or (negative) error code on failure.
- */
-int
-bt_mesh_model_publish(struct bt_mesh_model *model, struct os_mbuf *msg);
-
-/**
- * @}
- */
-
-/** Node Composition */
-struct bt_mesh_comp
-{
-    u16_t cid;u16_t pid;u16_t vid;
-
-    size_t elem_count;
-    struct bt_mesh_elem *elem;
-};
-
-/**
- * @}
- */
-
-/**
- * @brief Bluetooth Mesh Provisioning
- * @defgroup bt_mesh_prov Bluetooth Mesh Provisioning
- * @ingroup bt_mesh
- * @{
- */
-
-typedef enum
-{
-    BT_MESH_NO_OUTPUT = 0,
-    BT_MESH_BLINK = BIT(0),
-    BT_MESH_BEEP = BIT(1),
-    BT_MESH_VIBRATE = BIT(2),
-    BT_MESH_DISPLAY_NUMBER = BIT(3),
-    BT_MESH_DISPLAY_STRING = BIT(4),
-} bt_mesh_output_action;
-
-typedef enum
-{
-    BT_MESH_NO_INPUT = 0,
-    BT_MESH_PUSH = BIT(0),
-    BT_MESH_TWIST = BIT(1),
-    BT_MESH_ENTER_NUMBER = BIT(2),
-    BT_MESH_ENTER_STRING = BIT(3),
-} bt_mesh_input_action;
-
-struct bt_mesh_prov
-{
-    const u8_t *uuid;
-
-    u8_t *static_val;u8_t static_val_len;
-
-    u8_t output_size;u16_t output_actions;
-
-    u8_t input_size;u16_t input_actions;
-
-    int
-    (*output_number)(bt_mesh_output_action act, u32_t num);
-    int
-    (*output_string)(const char *str);
-
-    int
-    (*input)(bt_mesh_input_action act, u8_t size);
-
-    void
-    (*complete)(void);
-};
-
-int
-bt_mesh_input_string(const char *str);
-int
-bt_mesh_input_number(u32_t num);
-
-/**
- * @}
- */
-
-/**
- * @brief Bluetooth Mesh
- * @defgroup bt_mesh Bluetooth Mesh
- * @ingroup bluetooth
- * @{
- */
-
-/* Primary Network Key index */
-#define BT_MESH_NET_PRIMARY                 0x000
-
-#define BT_MESH_RELAY_DISABLED              0x00
-#define BT_MESH_RELAY_ENABLED               0x01
-#define BT_MESH_RELAY_NOT_SUPPORTED         0x02
-
-#define BT_MESH_BEACON_DISABLED             0x00
-#define BT_MESH_BEACON_ENABLED              0x01
-
-#define BT_MESH_GATT_PROXY_DISABLED         0x00
-#define BT_MESH_GATT_PROXY_ENABLED          0x01
-#define BT_MESH_GATT_PROXY_NOT_SUPPORTED    0x02
-
-#define BT_MESH_FRIEND_DISABLED             0x00
-#define BT_MESH_FRIEND_ENABLED              0x01
-#define BT_MESH_FRIEND_NOT_SUPPORTED        0x02
-
-#define BT_MESH_NODE_IDENTITY_STOPPED       0x00
-#define BT_MESH_NODE_IDENTITY_RUNNING       0x01
-#define BT_MESH_NODE_IDENTITY_NOT_SUPPORTED 0x02
-
-/* Features */
-#define BT_MESH_FEAT_RELAY                  BIT(0)
-#define BT_MESH_FEAT_PROXY                  BIT(1)
-#define BT_MESH_FEAT_FRIEND                 BIT(2)
-#define BT_MESH_FEAT_LOW_POWER              BIT(3)
-
-/** @brief Initialize Mesh support
- *
- *  @param own_addr_type    Node address type
- *  @param prov Node provisioning information.
- *  @param comp Node Composition.
- *
- *  @return Zero on success or (negative) error code otherwise.
- */
-int
-bt_mesh_init(uint8_t own_addr_type, const struct bt_mesh_prov *prov,
-             const struct bt_mesh_comp *comp);
-
-/** @brief Reset the state of the local Mesh node.
- *
- *  Resets the state of the node, which in turn causes it to start sending
- *  out unprovisioned beacons.
- */
-void
-bt_mesh_reset(void);
-
-/** @brief Provision the local Mesh Node.
- *
- *  This API should normally not be used directly by the application. The
- *  only exception is for testing purposes where manual provisioning is
- *  desired without an actual external provisioner.
- *
- *  @param net_key  Network Key
- *  @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]);
-
-/** @brief Toggle the Low Power feature of the local device
- *
- *  Enables or disables the Low Power feature of the local device. This is
- *  exposed as a run-time feature, since the device might want to change
- *  this e.g. based on being plugged into a stable power source or running
- *  from a battery power source.
- *
- *  @param enable  true to enable LPN functionality, false to disable it.
- *
- *  @return Zero on success or (negative) error code otherwise.
- */
-int
-bt_mesh_lpn_set(bool enable);
-
-/**
- * @}
- */
+#include "syscfg/syscfg.h"
+#include "os/os_mbuf.h"
+
+#include "glue.h"
+#include "access.h"
+#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/net/nimble/host/mesh/include/mesh/proxy.h b/net/nimble/host/mesh/include/mesh/proxy.h
new file mode 100644
index 000000000..55c74e902
--- /dev/null
+++ b/net/nimble/host/mesh/include/mesh/proxy.h
@@ -0,0 +1,35 @@
+/** @file
+ *  @brief Bluetooth Mesh Proxy APIs.
+ */
+
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef __BT_MESH_PROXY_H
+#define __BT_MESH_PROXY_H
+
+/**
+ * @brief Bluetooth Mesh Proxy
+ * @defgroup bt_mesh_proxy Bluetooth Mesh Proxy
+ * @ingroup bt_mesh
+ * @{
+ */
+
+/**
+ * @brief Enable advertising with Node Identity.
+ *
+ * This API requires that GATT Proxy support has been enabled. Once called
+ * each subnet will start advertising using Node Identity for the next
+ * 60 seconds.
+ *
+ * @return 0 on success, or (negative) error code on failure.
+ */
+int bt_mesh_proxy_identity_enable(void);
+
+/**
+ * @}
+ */
+
+#endif /* __BT_MESH_PROXY_H */
diff --git a/net/nimble/host/mesh/include/mesh/slist.h b/net/nimble/host/mesh/include/mesh/slist.h
new file mode 100644
index 000000000..79f845755
--- /dev/null
+++ b/net/nimble/host/mesh/include/mesh/slist.h
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2016 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @file
+ *
+ * @brief Single-linked list implementation
+ *
+ * Single-linked list implementation using inline macros/functions.
+ * This API is not thread safe, and thus if a list is used across threads,
+ * calls to functions must be protected with synchronization primitives.
+ */
+
+#ifndef __SLIST_H__
+#define __SLIST_H__
+
+#include <stddef.h>
+#include <stdbool.h>
+#include "os/queue.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct os_mbuf sys_snode_t;
+
+STAILQ_HEAD(_slist, os_mbuf_pkthdr);
+
+typedef struct _slist sys_slist_t;
+
+/**
+ * @brief Provide the primitive to iterate on a list
+ * Note: the loop is unsafe and thus __sn should not be removed
+ *
+ * User _MUST_ add the loop statement curly braces enclosing its own code:
+ *
+ *     SYS_SLIST_FOR_EACH_NODE(l, n) {
+ *         <user code>
+ *     }
+ *
+ * This and other SYS_SLIST_*() macros are not thread safe.
+ *
+ * @param __sl A pointer on a sys_slist_t to iterate on
+ * @param __sn A sys_snode_t pointer to peek each node of the list
+ */
+#define SYS_SLIST_FOR_EACH_NODE(__sl, __sn)				\
+	for (__sn = sys_slist_peek_head(__sl); __sn;			\
+	     __sn = sys_slist_peek_next(__sn))
+
+/**
+ * @brief Provide the primitive to iterate on a list under a container
+ * Note: the loop is unsafe and thus __cn should not be detached
+ *
+ * User _MUST_ add the loop statement curly braces enclosing its own code:
+ *
+ *     SYS_SLIST_FOR_EACH_CONTAINER(l, c, n) {
+ *         <user code>
+ *     }
+ *
+ * @param __sl A pointer on a sys_slist_t to iterate on
+ * @param __cn A pointer to peek each entry of the list
+ * @param __n The field name of sys_node_t within the container struct
+ */
+#define SYS_SLIST_FOR_EACH_CONTAINER(__sl, __cn, __n)			\
+	STAILQ_FOREACH(__cn, __sl, omp_next)
+
+/**
+ * @brief Initialize a list
+ *
+ * @param list A pointer on the list to initialize
+ */
+void sys_slist_init(sys_slist_t *list);
+
+#define SYS_SLIST_STATIC_INIT(ptr_to_list) STAILQ_HEAD_INITIALIZER(ptr_to_list)
+
+/**
+ * @brief Test if the given list is empty
+ *
+ * @param list A pointer on the list to test
+ *
+ * @return a boolean, true if it's empty, false otherwise
+ */
+bool sys_slist_is_empty(sys_slist_t *list);
+
+/**
+ * @brief Peek the first node from the list
+ *
+ * @param list A point on the list to peek the first node from
+ *
+ * @return A pointer on the first node of the list (or NULL if none)
+ */
+sys_snode_t *sys_slist_peek_head(sys_slist_t *list);
+
+/**
+ * @brief Peek the last node from the list
+ *
+ * @param list A point on the list to peek the last node from
+ *
+ * @return A pointer on the last node of the list (or NULL if none)
+ */
+sys_snode_t *sys_slist_peek_tail(sys_slist_t *list);
+
+/**
+ * @brief Peek the next node from current node, node is not NULL
+ *
+ * Faster then sys_slist_peek_next() if node is known not to be NULL.
+ *
+ * @param node A pointer on the node where to peek the next node
+ *
+ * @return a pointer on the next node (or NULL if none)
+ */
+sys_snode_t *sys_slist_peek_next_no_check(sys_snode_t *node);
+
+/**
+ * @brief Peek the next node from current node
+ *
+ * @param node A pointer on the node where to peek the next node
+ *
+ * @return a pointer on the next node (or NULL if none)
+ */
+sys_snode_t *sys_slist_peek_next(sys_snode_t *node);
+
+/**
+ * @brief Prepend a node to the given list
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param node A pointer on the node to prepend
+ */
+void sys_slist_prepend(sys_slist_t *list, sys_snode_t *node);
+
+/**
+ * @brief Append a node to the given list
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param node A pointer on the node to append
+ */
+void sys_slist_append(sys_slist_t *list, sys_snode_t *node);
+
+/**
+ * @brief Append a list to the given list
+ *
+ * Append a singly-linked, NULL-terminated list consisting of nodes containing
+ * the pointer to the next node as the first element of a node, to @a list.
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param head A pointer to the first element of the list to append
+ * @param tail A pointer to the last element of the list to append
+ */
+void sys_slist_append_list(sys_slist_t *list, sys_slist_t *list_append);
+
+/**
+ * @brief merge two slists, appending the second one to the first
+ *
+ * When the operation is completed, the appending list is empty.
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param list_to_append A pointer to the list to append.
+ */
+void sys_slist_merge_slist(sys_slist_t *list, sys_slist_t *list_to_append);
+
+/**
+ * @brief Insert a node to the given list
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param prev A pointer on the previous node
+ * @param node A pointer on the node to insert
+ */
+void sys_slist_insert(sys_slist_t *list,
+				    sys_snode_t *prev,
+				    sys_snode_t *node);
+
+/**
+ * @brief Fetch and remove the first node of the given list
+ *
+ * List must be known to be non-empty.
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ *
+ * @return A pointer to the first node of the list
+ */
+sys_snode_t *sys_slist_get_not_empty(sys_slist_t *list);
+/**
+ * @brief Fetch and remove the first node of the given list
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ *
+ * @return A pointer to the first node of the list (or NULL if empty)
+ */
+sys_snode_t *sys_slist_get(sys_slist_t *list);
+
+/**
+ * @brief Remove a node
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param prev_node A pointer on the previous node
+ *        (can be NULL, which means the node is the list's head)
+ * @param node A pointer on the node to remove
+ */
+void sys_slist_remove(sys_slist_t *list,
+				    sys_snode_t *prev_node,
+				    sys_snode_t *node);
+
+/**
+ * @brief Find and remove a node from a list
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param node A pointer on the node to remove from the list
+ *
+ * @return true if node was removed
+ */
+bool sys_slist_find_and_remove(sys_slist_t *list,
+					     sys_snode_t *node);
+
+void net_buf_slist_put(sys_slist_t *list, struct os_mbuf *buf);
+
+struct os_mbuf *net_buf_slist_get(sys_slist_t *list);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __SLIST_H__ */
diff --git a/net/nimble/host/mesh/pkg.yml b/net/nimble/host/mesh/pkg.yml
index 57993e948..d3c36be1d 100644
--- a/net/nimble/host/mesh/pkg.yml
+++ b/net/nimble/host/mesh/pkg.yml
@@ -33,6 +33,9 @@ pkg.deps:
     - net/nimble/host  
     - crypto/tinycrypt
 
+pkg.deps.BLE_MESH_SHELL:
+    - sys/shell
+
 pkg.req_apis:
     - log
     - stats
diff --git a/net/nimble/host/mesh/src/access.c b/net/nimble/host/mesh/src/access.c
index 5580f27e9..e8bef8049 100644
--- a/net/nimble/host/mesh/src/access.c
+++ b/net/nimble/host/mesh/src/access.c
@@ -30,8 +30,14 @@ static const struct {
 	const u16_t id;
 	int (*const init)(struct bt_mesh_model *model, bool primary);
 } model_init[] = {
-	{ BT_MESH_MODEL_ID_CFG_SRV, bt_mesh_conf_init },
-	{ BT_MESH_MODEL_ID_HEALTH_SRV, bt_mesh_health_init },
+	{ BT_MESH_MODEL_ID_CFG_SRV, bt_mesh_cfg_srv_init },
+	{ BT_MESH_MODEL_ID_HEALTH_SRV, bt_mesh_health_srv_init },
+#if MYNEWT_VAL(BLE_MESH_CFG_CLI)
+	{ BT_MESH_MODEL_ID_CFG_CLI, bt_mesh_cfg_cli_init },
+#endif
+#if MYNEWT_VAL(BLE_MESH_HEALTH_CLI)
+	{ BT_MESH_MODEL_ID_HEALTH_CLI, bt_mesh_health_cli_init },
+#endif
 };
 
 void bt_mesh_model_foreach(void (*func)(struct bt_mesh_model *mod,
@@ -91,21 +97,135 @@ s32_t bt_mesh_model_pub_period_get(struct bt_mesh_model *mod)
 	return period >> mod->pub->period_div;
 }
 
+static s32_t next_period(struct bt_mesh_model *mod)
+{
+	struct bt_mesh_model_pub *pub = mod->pub;
+	u32_t elapsed, period;
+
+	period = bt_mesh_model_pub_period_get(mod);
+	if (!period) {
+		return 0;
+	}
+
+	elapsed = k_uptime_get_32() - pub->period_start;
+
+	BT_DBG("Publishing took %ums", elapsed);
+
+	if (elapsed > period) {
+		BT_WARN("Publication sending took longer than the period");
+		/* Return smallest positive number since 0 means disabled */
+		return K_MSEC(1);
+	}
+
+	return period - elapsed;
+}
+
+static void publish_sent(int err, void *user_data)
+{
+	struct bt_mesh_model *mod = user_data;
+	s32_t delay;
+
+	BT_DBG("err %d", err);
+
+	if (mod->pub->count) {
+		delay = BT_MESH_PUB_TRANSMIT_INT(mod->pub->retransmit);
+	} else {
+		delay = next_period(mod);
+	}
+
+	if (delay) {
+		BT_DBG("Publishing next time in %dms", delay);
+		k_delayed_work_submit(&mod->pub->timer, delay);
+	}
+}
+
+static const struct bt_mesh_send_cb pub_sent_cb = {
+	.end = publish_sent,
+};
+
+static int publish_retransmit(struct bt_mesh_model *mod)
+{
+	struct os_mbuf *sdu = NET_BUF_SIMPLE(BT_MESH_TX_SDU_MAX);
+	struct bt_mesh_model_pub *pub = mod->pub;
+	struct bt_mesh_app_key *key;
+	struct bt_mesh_msg_ctx ctx = {
+		.addr = pub->addr,
+		.send_ttl = pub->ttl,
+	};
+	struct bt_mesh_net_tx tx = {
+		.ctx = &ctx,
+		.src = mod->elem->addr,
+		.xmit = bt_mesh_net_transmit_get(),
+		.friend_cred = pub->cred,
+	};
+
+	key = bt_mesh_app_key_find(pub->key);
+	if (!key) {
+		return -EADDRNOTAVAIL;
+	}
+
+	tx.sub = bt_mesh_subnet_get(key->net_idx);
+
+	ctx.net_idx = key->net_idx;
+	ctx.app_idx = key->app_idx;
+
+	net_buf_simple_init(sdu, 0);
+	net_buf_simple_add_mem(sdu, pub->msg->om_data, pub->msg->om_len);
+
+	pub->count--;
+
+	return bt_mesh_trans_send(&tx, sdu, &pub_sent_cb, mod);
+}
+
 static void mod_publish(struct os_event *work)
 {
 	struct bt_mesh_model_pub *pub = work->ev_arg;
 	s32_t period_ms;
+	int err;
 
 	BT_DBG("");
 
 	period_ms = bt_mesh_model_pub_period_get(pub->mod);
 	BT_DBG("period %u ms", period_ms);
-	if (period_ms) {
-		k_delayed_work_submit(&pub->timer, period_ms);
+
+	if (pub->count) {
+		err = publish_retransmit(pub->mod);
+		if (err) {
+			BT_ERR("Failed to retransmit (err %d)", err);
+
+			pub->count = 0;
+
+			/* Continue with normal publication */
+			if (period_ms) {
+				k_delayed_work_submit(&pub->timer, period_ms);
+			}
+		}
+
+		return;
+	}
+
+	if (!period_ms) {
+		return;
+	}
+
+	__ASSERT_NO_MSG(pub->update != NULL);
+
+	pub->period_start = k_uptime_get_32();
+
+	err = pub->update(pub->mod);
+	if (err) {
+		BT_ERR("Failed to update publication message");
+		return;
 	}
 
-	if (pub->func) {
-		pub->func(pub->mod);
+	err = bt_mesh_model_publish(pub->mod);
+	if (err) {
+		BT_ERR("Publishing failed (err %d)", err);
+	}
+
+	if (pub->count) {
+		/* Retransmissions also control the timer */
+		k_delayed_work_cancel(&pub->timer);
 	}
 }
 
@@ -433,26 +553,20 @@ void bt_mesh_model_msg_init(struct os_mbuf *msg, u32_t opcode)
 	net_buf_simple_add_le16(msg, opcode & 0xffff);
 }
 
-int bt_mesh_model_send(struct bt_mesh_model *model,
-		       struct bt_mesh_msg_ctx *ctx,
-		       struct os_mbuf *msg, bt_mesh_cb_t cb,
-		       void *cb_data)
+static int model_send(struct bt_mesh_model *model,
+		      struct bt_mesh_net_tx *tx, bool implicit_bind,
+		      struct os_mbuf *msg,
+		      const struct bt_mesh_send_cb *cb, void *cb_data)
 {
-	struct bt_mesh_net_tx tx = {
-		.sub = bt_mesh_subnet_get(ctx->net_idx),
-		.ctx = ctx,
-		.src = model->elem->addr,
-	};
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x dst 0x%04x", tx->ctx->net_idx,
+	       tx->ctx->app_idx, tx->ctx->addr);
+	BT_DBG("len %u: %s", msg->om_len, bt_hex(msg->om_data, msg->om_len));
 
-	if (ctx->friend_cred && !bt_mesh_lpn_established()) {
-		BT_ERR("Friendship Credentials requested without a Friend");
-		return -EINVAL;
+	if (!bt_mesh_is_provisioned()) {
+		BT_ERR("Local node is not yet provisioned");
+		return -EAGAIN;
 	}
 
-	BT_DBG("net_idx 0x%04x app_idx 0x%04x dst 0x%04x", ctx->net_idx,
-	       ctx->app_idx, ctx->addr);
-	BT_DBG("len %u: %s", msg->om_len, bt_hex(msg->om_data, msg->om_len));
-
 	if (net_buf_simple_tailroom(msg) < 4) {
 		BT_ERR("Not enough tailroom for TransMIC");
 		return -EINVAL;
@@ -463,41 +577,92 @@ int bt_mesh_model_send(struct bt_mesh_model *model,
 		return -EMSGSIZE;
 	}
 
-	if (!model_has_key(model, ctx->app_idx)) {
-		BT_ERR("Model not bound to AppKey 0x%04x", ctx->app_idx);
+	if (!implicit_bind && !model_has_key(model, tx->ctx->app_idx)) {
+		BT_ERR("Model not bound to AppKey 0x%04x", tx->ctx->app_idx);
 		return -EINVAL;
 	}
 
-	return bt_mesh_trans_send(&tx, msg, cb, cb_data);
+	return bt_mesh_trans_send(tx, msg, cb, cb_data);
+}
+
+int bt_mesh_model_send(struct bt_mesh_model *model,
+		       struct bt_mesh_msg_ctx *ctx,
+		       struct os_mbuf *msg,
+		       const struct bt_mesh_send_cb *cb, void *cb_data)
+{
+	struct bt_mesh_net_tx tx = {
+		.sub = bt_mesh_subnet_get(ctx->net_idx),
+		.ctx = ctx,
+		.src = model->elem->addr,
+		.xmit = bt_mesh_net_transmit_get(),
+		.friend_cred = 0,
+	};
+
+	return model_send(model, &tx, false, msg, cb, cb_data);
 }
 
-int bt_mesh_model_publish(struct bt_mesh_model *model,
-			  struct os_mbuf *msg)
+int bt_mesh_model_publish(struct bt_mesh_model *model)
 {
+	struct os_mbuf *sdu = NET_BUF_SIMPLE(BT_MESH_TX_SDU_MAX);
+	struct bt_mesh_model_pub *pub = model->pub;
 	struct bt_mesh_app_key *key;
-	struct bt_mesh_msg_ctx ctx;
+	struct bt_mesh_msg_ctx ctx = {
+	};
+	struct bt_mesh_net_tx tx = {
+		.ctx = &ctx,
+		.src = model->elem->addr,
+		.xmit = bt_mesh_net_transmit_get(),
+	};
+	int err;
+
+	BT_DBG("");
 
-	if (!model->pub) {
+	if (!pub) {
 		return -ENOTSUP;
 	}
 
-	if (model->pub->key == BT_MESH_KEY_UNUSED ||
-	    model->pub->addr == BT_MESH_ADDR_UNASSIGNED) {
+	if (pub->addr == BT_MESH_ADDR_UNASSIGNED) {
 		return -EADDRNOTAVAIL;
 	}
 
-	key = bt_mesh_app_key_find(model->pub->key);
+	key = bt_mesh_app_key_find(pub->key);
 	if (!key) {
 		return -EADDRNOTAVAIL;
 	}
 
+	if (pub->msg->om_len + 4 > BT_MESH_TX_SDU_MAX) {
+		BT_ERR("Message does not fit maximum SDU size");
+		return -EMSGSIZE;
+	}
+
+	if (pub->count) {
+		BT_WARN("Clearing publish retransmit timer");
+		k_delayed_work_cancel(&pub->timer);
+	}
+
+	net_buf_simple_init(sdu, 0);
+	net_buf_simple_add_mem(sdu, pub->msg->om_data, pub->msg->om_len);
+
+	ctx.addr = pub->addr;
+	ctx.send_ttl = pub->ttl;
 	ctx.net_idx = key->net_idx;
 	ctx.app_idx = key->app_idx;
-	ctx.addr = model->pub->addr;
-	ctx.friend_cred = model->pub->cred;
-	ctx.send_ttl = model->pub->ttl;
 
-	return bt_mesh_model_send(model, &ctx, msg, NULL, NULL);
+	tx.friend_cred = pub->cred;
+	tx.sub = bt_mesh_subnet_get(ctx.net_idx),
+
+	pub->count = BT_MESH_PUB_TRANSMIT_COUNT(pub->retransmit);
+
+	BT_DBG("Publish Retransmit Count %u Interval %ums", pub->count,
+	       BT_MESH_PUB_TRANSMIT_INT(pub->retransmit));
+
+	err = model_send(model, &tx, true, sdu, &pub_sent_cb, model);
+	if (err) {
+		pub->count = 0;
+		return err;
+	}
+
+	return 0;
 }
 
 struct bt_mesh_model *bt_mesh_model_find_vnd(struct bt_mesh_elem *elem,
diff --git a/net/nimble/host/mesh/src/access.h b/net/nimble/host/mesh/src/access.h
index bf07ad6ea..638b6ff68 100644
--- a/net/nimble/host/mesh/src/access.h
+++ b/net/nimble/host/mesh/src/access.h
@@ -11,51 +11,38 @@
 
 #include "mesh/mesh.h"
 
-void
-bt_mesh_elem_register(struct bt_mesh_elem *elem, u8_t count);
+void bt_mesh_elem_register(struct bt_mesh_elem *elem, u8_t count);
 
-u8_t
-bt_mesh_elem_count(void);
+u8_t bt_mesh_elem_count(void);
 
 /* Find local element based on unicast or group address */
-struct bt_mesh_elem *
-bt_mesh_elem_find(u16_t addr);
+struct bt_mesh_elem *bt_mesh_elem_find(u16_t addr);
 
-struct bt_mesh_model *
-bt_mesh_model_find_vnd(struct bt_mesh_elem *elem, u16_t company, u16_t id);
-struct bt_mesh_model *
-bt_mesh_model_find(struct bt_mesh_elem *elem, u16_t id);
+struct bt_mesh_model *bt_mesh_model_find_vnd(struct bt_mesh_elem *elem,
+					     u16_t company, u16_t id);
+struct bt_mesh_model * bt_mesh_model_find(struct bt_mesh_elem *elem,
+					  u16_t id);
 
-u16_t *
-bt_mesh_model_find_group(struct bt_mesh_model *mod, u16_t addr);
+u16_t *bt_mesh_model_find_group(struct bt_mesh_model *mod, u16_t addr);
 
-bool
-bt_mesh_fixed_group_match(u16_t addr);
+bool bt_mesh_fixed_group_match(u16_t addr);
 
-void
-bt_mesh_model_foreach(
-        void
-        (*func)(struct bt_mesh_model *mod, struct bt_mesh_elem *elem, bool vnd,
-                bool primary, void *user_data),
-        void *user_data);
+void bt_mesh_model_foreach(void (*func)(struct bt_mesh_model *mod,
+					struct bt_mesh_elem *elem,
+					bool vnd, bool primary,
+					void *user_data),
+			   void *user_data);
 
-s32_t
-bt_mesh_model_pub_period_get(struct bt_mesh_model *mod);
+s32_t bt_mesh_model_pub_period_get(struct bt_mesh_model *mod);
 
-void
-bt_mesh_comp_provision(u16_t addr);
-void
-bt_mesh_comp_unprovision(void);
+void bt_mesh_comp_provision(u16_t addr);
+void bt_mesh_comp_unprovision(void);
 
-u16_t
-bt_mesh_primary_addr(void);
+u16_t bt_mesh_primary_addr(void);
 
-const struct bt_mesh_comp *
-bt_mesh_comp_get(void);
+const struct bt_mesh_comp *bt_mesh_comp_get(void);
 
-void
-bt_mesh_model_recv(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
+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);
+int bt_mesh_comp_register(const struct bt_mesh_comp *comp);
 #endif
diff --git a/net/nimble/host/mesh/src/adv.c b/net/nimble/host/mesh/src/adv.c
index 8c30f4406..b4b3adebb 100644
--- a/net/nimble/host/mesh/src/adv.c
+++ b/net/nimble/host/mesh/src/adv.c
@@ -46,129 +46,143 @@ static struct os_eventq adv_queue;
 extern u8_t g_mesh_addr_type;
 
 static os_membuf_t adv_buf_mem[OS_MEMPOOL_SIZE(
-        MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT),
-        BT_MESH_ADV_DATA_SIZE + BT_MESH_ADV_USER_DATA_SIZE)];
+		MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT),
+		BT_MESH_ADV_DATA_SIZE + BT_MESH_MBUF_HEADER_SIZE)];
 
 struct os_mbuf_pool adv_os_mbuf_pool;
 static struct os_mempool adv_buf_mempool;
 
 static const u8_t adv_type[] = {
-    [BT_MESH_ADV_PROV] = BLE_HS_ADV_TYPE_MESH_PROV,
-    [BT_MESH_ADV_DATA] = BLE_HS_ADV_TYPE_MESH_MESSAGE,
-    [BT_MESH_ADV_BEACON] = BLE_HS_ADV_TYPE_MESH_BEACON,
+	[BT_MESH_ADV_PROV] = BLE_HS_ADV_TYPE_MESH_PROV,
+	[BT_MESH_ADV_DATA] = BLE_HS_ADV_TYPE_MESH_MESSAGE,
+	[BT_MESH_ADV_BEACON] = BLE_HS_ADV_TYPE_MESH_BEACON,
 };
 
 
-static inline void adv_sent(struct os_mbuf *buf, int err)
+static struct bt_mesh_adv adv_pool[CONFIG_BT_MESH_ADV_BUF_COUNT];
+
+static struct bt_mesh_adv *adv_alloc(int id)
 {
-	if (BT_MESH_ADV(buf)->busy) {
-		BT_MESH_ADV(buf)->busy = 0;
+	return &adv_pool[id];
+}
 
-		if (BT_MESH_ADV(buf)->sent) {
-			BT_MESH_ADV(buf)->sent(buf, err);
-		}
+static inline void adv_send_start(u16_t duration, int err,
+				  const struct bt_mesh_send_cb *cb,
+				  void *cb_data)
+{
+	if (cb && cb->start) {
+		cb->start(duration, err, cb_data);
 	}
+}
 
-	net_buf_unref(buf);
+static inline void adv_send_end(int err, const struct bt_mesh_send_cb *cb,
+				void *cb_data)
+{
+	if (cb && cb->end) {
+		cb->end(err, cb_data);
+	}
 }
 
 static inline void adv_send(struct os_mbuf *buf)
 {
-    /* XXX: For BT5 we could have better adv interval */
-    const s32_t adv_int_min =  ADV_INT_DEFAULT;
-    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 = (adv->count + 1) * (adv_int + 10);
-
-    BT_DBG("buf %p, type %u len %u:", buf, adv->type,
-           buf->om_len);
-    BT_DBG("count %u interval %ums duration %ums",
-               adv->count + 1, adv_int, duration);
-
-    ad.type = adv_type[BT_MESH_ADV(buf)->type];
-    ad.data_len = buf->om_len;
-    ad.data = buf->om_data;
-
-    param.itvl_min = ADV_INT(adv_int);
-    param.itvl_max = param.itvl_min;
-    param.conn_mode = BLE_GAP_CONN_MODE_NON;
-
-    err = bt_le_adv_start(&param, &ad, 1, NULL, 0);
-    adv_sent(buf, err);
-    if (err) {
-        BT_ERR("Advertising failed: err %d", err);
-        return;
-    }
-
-    BT_DBG("Advertising started. Sleeping %u ms", duration);
-
-    os_time_delay(OS_TICKS_PER_SEC * duration / 1000);
-
-    err = bt_le_adv_stop();
-    if (err) {
-        BT_ERR("Stopping advertising failed: err %d", err);
-        return;
-    }
-
-    BT_DBG("Advertising stopped");
+	const struct bt_mesh_send_cb *cb = BT_MESH_ADV(buf)->cb;
+	void *cb_data = BT_MESH_ADV(buf)->cb_data;
+	/* XXX: For BT5 we could have better adv interval */
+	const s32_t adv_int_min =  ADV_INT_DEFAULT;
+	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 = (adv->count + 1) * (adv_int + 10);
+
+	BT_DBG("buf %p, type %u len %u:", buf, adv->type,
+	       buf->om_len);
+	BT_DBG("count %u interval %ums duration %ums",
+	       adv->count + 1, adv_int, duration);
+
+	ad.type = adv_type[BT_MESH_ADV(buf)->type];
+	ad.data_len = buf->om_len;
+	ad.data = buf->om_data;
+
+	param.itvl_min = ADV_INT(adv_int);
+	param.itvl_max = param.itvl_min;
+	param.conn_mode = BLE_GAP_CONN_MODE_NON;
+
+	err = bt_le_adv_start(&param, &ad, 1, NULL, 0);
+	net_buf_unref(buf);
+	adv_send_start(duration, err, cb, cb_data);
+	if (err) {
+		BT_ERR("Advertising failed: err %d", err);
+		return;
+	}
+
+	BT_DBG("Advertising started. Sleeping %u ms", duration);
+
+	os_time_delay(OS_TICKS_PER_SEC * duration / 1000);
+
+	err = bt_le_adv_stop();
+	adv_send_end(err, cb, cb_data);
+	if (err) {
+		BT_ERR("Stopping advertising failed: err %d", err);
+		return;
+	}
+
+	BT_DBG("Advertising stopped");
 }
 
 static void
 adv_thread(void *args)
 {
-    static struct os_event *ev;
-    struct os_mbuf *adv_data;
-    struct bt_mesh_adv *adv;
+	static struct os_event *ev;
+	struct os_mbuf *buf;
 #if (MYNEWT_VAL(BLE_MESH_PROXY))
-    s32_t timeout;
-    struct os_eventq *eventq_pool = &adv_queue;
+	s32_t timeout;
+	struct os_eventq *eventq_pool = &adv_queue;
 #endif
 
-    BT_DBG("started");
+	BT_DBG("started");
 
-    while (1) {
+	while (1) {
 #if (MYNEWT_VAL(BLE_MESH_PROXY))
-        ev = os_eventq_get_no_wait(&adv_queue);
-        while (!ev) {
-            timeout = bt_mesh_proxy_adv_start();
-            BT_DBG("Proxy Advertising up to %d ms", timeout);
-
-            // FIXME: should we redefine K_SECONDS macro instead in glue?
-            if (timeout != K_FOREVER) {
-                timeout = OS_TICKS_PER_SEC * timeout / 1000;
-            }
-
-            ev = os_eventq_poll(&eventq_pool, 1, timeout);
-            bt_mesh_proxy_adv_stop();
-        }
+		ev = os_eventq_get_no_wait(&adv_queue);
+		while (!ev) {
+			timeout = bt_mesh_proxy_adv_start();
+			BT_DBG("Proxy Advertising up to %d ms", timeout);
+
+			// FIXME: should we redefine K_SECONDS macro instead in glue?
+			if (timeout != K_FOREVER) {
+				timeout = OS_TICKS_PER_SEC * timeout / 1000;
+			}
+
+			ev = os_eventq_poll(&eventq_pool, 1, timeout);
+			bt_mesh_proxy_adv_stop();
+		}
 #else
-        ev = os_eventq_get(&adv_queue);
+		ev = os_eventq_get(&adv_queue);
 #endif
 
-        if (!ev || !ev->ev_arg) {
-            continue;
-        }
+		if (!ev || !ev->ev_arg) {
+			continue;
+		}
 
-        adv_data = ev->ev_arg;
-        adv = BT_MESH_ADV(adv_data);
+		buf = ev->ev_arg;
 
-        /* busy == 0 means this was canceled */
-        if (adv->busy) {
-            adv_send(adv_data);
-        }
+		/* busy == 0 means this was canceled */
+		if (BT_MESH_ADV(buf)->busy) {
+			BT_MESH_ADV(buf)->busy = 0;
+			adv_send(buf);
+		}
 
-        os_sched(NULL);
-    }
+		os_sched(NULL);
+	}
 }
 
 void bt_mesh_adv_update(void)
 {
-    static struct os_event ev = { };
+	static struct os_event ev = { };
 
 	BT_DBG("");
 
@@ -176,43 +190,47 @@ 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)
 {
-    struct os_mbuf *adv_data;
-    struct bt_mesh_adv *adv;
-
-    adv_data = os_mbuf_get_pkthdr(pool, sizeof(struct bt_mesh_adv));
-    if (!adv_data) {
-        return NULL;
-    }
-
-    adv = BT_MESH_ADV(adv_data);
-    memset(adv, 0, sizeof(*adv));
-
-    adv->type = type;
-    adv->count = xmit_count;
-    adv->adv_int = xmit_int;
-    adv->ref_cnt = 1;
-    adv->ev.ev_arg = adv_data;
-    return adv_data;
+	struct bt_mesh_adv *adv;
+	struct os_mbuf *buf;
+
+	buf = os_mbuf_get_pkthdr(pool, BT_MESH_ADV_USER_DATA_SIZE);
+	if (!buf) {
+		return NULL;
+	}
+
+	adv = get_id(net_buf_id(buf));
+	BT_MESH_ADV(buf) = adv;
+
+	memset(adv, 0, sizeof(*adv));
+
+	adv->type         = type;
+	adv->count        = xmit_count;
+	adv->adv_int      = xmit_int;
+	adv->ref_cnt = 1;
+	adv->ev.ev_arg = 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)
 {
-	return bt_mesh_adv_create_from_pool(&adv_os_mbuf_pool,
-					    type, xmit_count,
-					    xmit_int, timeout);
+	return bt_mesh_adv_create_from_pool(&adv_os_mbuf_pool, adv_alloc, type,
+					    xmit_count, xmit_int, timeout);
 }
 
-void bt_mesh_adv_send(struct os_mbuf *buf, bt_mesh_adv_func_t sent)
+void bt_mesh_adv_send(struct os_mbuf *buf, const struct bt_mesh_send_cb *cb,
+		      void *cb_data)
 {
 	BT_DBG("buf %p, type 0x%02x len %u: %s", buf, BT_MESH_ADV(buf)->type, buf->om_len,
 	       bt_hex(buf->om_data, buf->om_len));
 
-	BT_MESH_ADV(buf)->sent = sent;
+	BT_MESH_ADV(buf)->cb = cb;
+	BT_MESH_ADV(buf)->cb_data = cb_data;
 	BT_MESH_ADV(buf)->busy = 1;
 	BT_MESH_ADV(buf)->ev.ev_cb = NULL; /* does not matter */
 
@@ -220,7 +238,7 @@ void bt_mesh_adv_send(struct os_mbuf *buf, bt_mesh_adv_func_t sent)
 }
 
 static void bt_mesh_scan_cb(const bt_addr_le_t *addr, s8_t rssi,
-                            u8_t adv_type, struct os_mbuf *buf)
+			    u8_t adv_type, struct os_mbuf *buf)
 {
 	if (adv_type != BLE_HCI_ADV_TYPE_ADV_NONCONN_IND) {
 		return;
@@ -272,86 +290,86 @@ static void bt_mesh_scan_cb(const bt_addr_le_t *addr, s8_t rssi,
 
 void bt_mesh_adv_init(void)
 {
-    os_stack_t *pstack;
-    int rc;
+	os_stack_t *pstack;
+	int rc;
 
-    pstack = malloc(sizeof(os_stack_t) * ADV_STACK_SIZE);
-    assert(pstack);
+	pstack = malloc(sizeof(os_stack_t) * ADV_STACK_SIZE);
+	assert(pstack);
 
-    rc = os_mempool_init(&adv_buf_mempool, MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT),
-    BT_MESH_ADV_DATA_SIZE + BT_MESH_ADV_USER_DATA_SIZE,
-                         adv_buf_mem, "adv_buf_pool");
-    assert(rc == 0);
+	rc = os_mempool_init(&adv_buf_mempool, MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT),
+			     BT_MESH_ADV_DATA_SIZE + BT_MESH_MBUF_HEADER_SIZE,
+			     adv_buf_mem, "adv_buf_pool");
+	assert(rc == 0);
 
-    rc = os_mbuf_pool_init(&adv_os_mbuf_pool, &adv_buf_mempool,
-                           BT_MESH_ADV_DATA_SIZE + BT_MESH_ADV_USER_DATA_SIZE,
-                           MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT));
-    assert(rc == 0);
+	rc = os_mbuf_pool_init(&adv_os_mbuf_pool, &adv_buf_mempool,
+			       BT_MESH_ADV_DATA_SIZE + BT_MESH_MBUF_HEADER_SIZE,
+			       MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT));
+	assert(rc == 0);
 
-    os_eventq_init(&adv_queue);
+	os_eventq_init(&adv_queue);
 
-    os_task_init(&adv_task, "mesh_adv", adv_thread, NULL,
-                 MYNEWT_VAL(BLE_MESH_ADV_TASK_PRIO), OS_WAIT_FOREVER, pstack,
-                 ADV_STACK_SIZE);
+	os_task_init(&adv_task, "mesh_adv", adv_thread, NULL,
+		     MYNEWT_VAL(BLE_MESH_ADV_TASK_PRIO), OS_WAIT_FOREVER, pstack,
+		     ADV_STACK_SIZE);
 }
 
 int
 ble_adv_gap_mesh_cb(struct ble_gap_event *event, void *arg)
 {
 #if MYNEWT_VAL(BLE_EXT_ADV)
-    struct ble_gap_ext_disc_desc *ext_desc;
+	struct ble_gap_ext_disc_desc *ext_desc;
 #endif
-    struct ble_gap_disc_desc *desc;
-    struct os_mbuf *buf = NULL;
+	struct ble_gap_disc_desc *desc;
+	struct os_mbuf *buf = NULL;
 
 #if BT_MESH_EXTENDED_DEBUG
-    BT_DBG("event->type %d", event->type);
+	BT_DBG("event->type %d", event->type);
 #endif
 
-    switch (event->type) {
+	switch (event->type) {
 #if MYNEWT_VAL(BLE_EXT_ADV)
-        case BLE_GAP_EVENT_EXT_DISC:
-            ext_desc = &event->ext_disc;
-            buf = os_mbuf_get_pkthdr(&adv_os_mbuf_pool, 0);
-            if (!buf || os_mbuf_append(buf, ext_desc->data, ext_desc->length_data)) {
-                BT_ERR("Could not append data");
-                goto done;
-            }
-            bt_mesh_scan_cb(&ext_desc->addr, ext_desc->rssi,
-                            ext_desc->legacy_event_type, buf);
-            break;
+	case BLE_GAP_EVENT_EXT_DISC:
+		ext_desc = &event->ext_disc;
+		buf = os_mbuf_get_pkthdr(&adv_os_mbuf_pool, 0);
+		if (!buf || os_mbuf_append(buf, ext_desc->data, ext_desc->length_data)) {
+			BT_ERR("Could not append data");
+			goto done;
+		}
+		bt_mesh_scan_cb(&ext_desc->addr, ext_desc->rssi,
+				ext_desc->legacy_event_type, buf);
+		break;
 #endif
-        case BLE_GAP_EVENT_DISC:
-            desc = &event->disc;
-            buf = os_mbuf_get_pkthdr(&adv_os_mbuf_pool, 0);
-            if (!buf || os_mbuf_append(buf, desc->data, desc->length_data)) {
-                BT_ERR("Could not append data");
-                goto done;
-            }
-
-            bt_mesh_scan_cb(&desc->addr, desc->rssi, desc->event_type, buf);
-            break;
-        default:
-            break;
-    }
+	case BLE_GAP_EVENT_DISC:
+		desc = &event->disc;
+		buf = os_mbuf_get_pkthdr(&adv_os_mbuf_pool, 0);
+		if (!buf || os_mbuf_append(buf, desc->data, desc->length_data)) {
+			BT_ERR("Could not append data");
+			goto done;
+		}
+
+		bt_mesh_scan_cb(&desc->addr, desc->rssi, desc->event_type, buf);
+		break;
+	default:
+		break;
+	}
 
 done:
-    if (buf) {
-        os_mbuf_free_chain(buf);
-    }
+	if (buf) {
+		os_mbuf_free_chain(buf);
+	}
 
-    return 0;
+	return 0;
 }
 
 int bt_mesh_scan_enable(void)
 {
-    struct ble_gap_disc_params scan_param =
-        { .passive = 1, .filter_duplicates = 0, .itvl =
-        MESH_SCAN_INTERVAL, .window = MESH_SCAN_WINDOW };
+	struct ble_gap_disc_params scan_param =
+		{ .passive = 1, .filter_duplicates = 0, .itvl =
+		  MESH_SCAN_INTERVAL, .window = MESH_SCAN_WINDOW };
 
-    BT_DBG("");
+	BT_DBG("");
 
-    return ble_gap_disc(g_mesh_addr_type, BLE_HS_FOREVER, &scan_param, NULL, NULL);
+	return ble_gap_disc(g_mesh_addr_type, BLE_HS_FOREVER, &scan_param, NULL, NULL);
 }
 
 int bt_mesh_scan_disable(void)
diff --git a/net/nimble/host/mesh/src/adv.h b/net/nimble/host/mesh/src/adv.h
index 31d68296f..27930d59f 100644
--- a/net/nimble/host/mesh/src/adv.h
+++ b/net/nimble/host/mesh/src/adv.h
@@ -12,56 +12,63 @@
 /* Maximum advertising data payload for a single data type */
 #include "mesh/mesh.h"
 
+#define BT_MESH_ADV(om) (*(struct bt_mesh_adv **) OS_MBUF_USRHDR(om))
+
 #define BT_MESH_ADV_DATA_SIZE 31
-#define BT_MESH_ADV_USER_DATA_SIZE (sizeof(struct os_mbuf_pkthdr) + \
-                                    sizeof(struct bt_mesh_adv)) + \
-                                    sizeof(struct os_mbuf)
 
-#define BT_MESH_ADV(om) ((struct bt_mesh_adv *) OS_MBUF_USRHDR(om))
+/* The user data is a pointer (4 bytes) to struct bt_mesh_adv */
+#define BT_MESH_ADV_USER_DATA_SIZE 4
+
+#define BT_MESH_MBUF_HEADER_SIZE (sizeof(struct os_mbuf_pkthdr) + \
+                                    BT_MESH_ADV_USER_DATA_SIZE +\
+				    sizeof(struct os_mbuf))
 
 enum bt_mesh_adv_type
 {
-    BT_MESH_ADV_PROV, BT_MESH_ADV_DATA, BT_MESH_ADV_BEACON,
+	BT_MESH_ADV_PROV,
+	BT_MESH_ADV_DATA,
+	BT_MESH_ADV_BEACON,
 };
 
-typedef void
-(*bt_mesh_adv_func_t)(struct os_mbuf *adv_data, int err);
-
-struct bt_mesh_adv
-{
-    bt_mesh_adv_func_t sent;
-    u8_t type :2, busy :1;
-    u8_t count :3, adv_int :5;
-    union
-    {
-        /* Generic User Data */
-        u8_t user_data[2];
-
-        /* Address, used e.g. for Friend Queue messages */
-        u16_t addr;
-
-        /* For transport layer segment sending */
-        struct
-        {
-            u8_t tx_id;
-            u8_t attempts :6, new_key :1, friend_cred :1;
-        } seg;
-    };
-
-    int ref_cnt;
-    struct os_event ev;
+typedef void (*bt_mesh_adv_func_t)(struct os_mbuf *buf, u16_t duration,
+				   int err, void *user_data);
+
+struct bt_mesh_adv {
+	const struct bt_mesh_send_cb *cb;
+	void *cb_data;
+
+	u8_t      type:2,
+		  busy:1;
+	u8_t      count:3,
+		  adv_int:5;
+	union {
+		/* Address, used e.g. for Friend Queue messages */
+		u16_t addr;
+
+		/* For transport layer segment sending */
+		struct {
+			u8_t attempts;
+		} seg;
+	};
+
+	int ref_cnt;
+	struct os_event ev;
 };
 
+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);
+				    u8_t xmit_int, 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);
 
-void bt_mesh_adv_send(struct os_mbuf *buf, bt_mesh_adv_func_t sent);
+void bt_mesh_adv_send(struct os_mbuf *buf, const struct bt_mesh_send_cb *cb,
+		      void *cb_data);
 
 void bt_mesh_adv_update(void);
 
diff --git a/net/nimble/host/mesh/src/beacon.c b/net/nimble/host/mesh/src/beacon.c
index ad7464a93..ace6428fa 100644
--- a/net/nimble/host/mesh/src/beacon.c
+++ b/net/nimble/host/mesh/src/beacon.c
@@ -39,24 +39,18 @@
 
 static struct k_delayed_work beacon_timer;
 
-static struct {
-	u16_t net_idx;
-	u8_t  data[21];
-} beacon_cache[MYNEWT_VAL(BLE_MESH_SUBNET_COUNT)];
-
 static struct bt_mesh_subnet *cache_check(u8_t data[21])
 {
-	struct bt_mesh_subnet *sub;
 	int i;
 
-	for (i = 0; i < ARRAY_SIZE(beacon_cache); i++) {
-		if (memcmp(beacon_cache[i].data, data, 21)) {
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
+		struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
+
+		if (sub->net_idx == BT_MESH_KEY_UNUSED) {
 			continue;
 		}
 
-		sub = bt_mesh_subnet_get(beacon_cache[i].net_idx);
-		if (sub) {
-			BT_DBG("Match found in cache");
+		if (!memcmp(sub->beacon_cache, data, 21)) {
 			return sub;
 		}
 	}
@@ -64,23 +58,20 @@ static struct bt_mesh_subnet *cache_check(u8_t data[21])
 	return NULL;
 }
 
-static void cache_add(u8_t data[21], u16_t net_idx)
+static void cache_add(u8_t data[21], struct bt_mesh_subnet *sub)
 {
-	memcpy(beacon_cache[net_idx].data, data, 21);
+	memcpy(sub->beacon_cache, data, 21);
 }
 
-static void beacon_complete(struct os_mbuf *buf, int err)
+static void beacon_complete(int err, void *user_data)
 {
-	struct bt_mesh_subnet *sub;
+	struct bt_mesh_subnet *sub = user_data;
 
 	BT_DBG("err %d", err);
 
-	sub = &bt_mesh.sub[BT_MESH_ADV(buf)->user_data[0]];
-	sub->beacon_sent = k_uptime_get();
+	sub->beacon_sent = k_uptime_get_32();
 }
 
-#define BEACON_INTERVAL(sub) K_SECONDS(10 * ((sub)->beacons_last + 1))
-
 void bt_mesh_beacon_create(struct bt_mesh_subnet *sub,
 			   struct os_mbuf *buf)
 {
@@ -111,27 +102,32 @@ void bt_mesh_beacon_create(struct bt_mesh_subnet *sub,
 	       bt_hex(sub->auth, 8));
 }
 
+/* If the interval has passed or is within 5 seconds from now send a beacon */
+#define BEACON_THRESHOLD(sub) (K_SECONDS(10 * ((sub)->beacons_last + 1)) - \
+			       K_SECONDS(5))
+
 static int secure_beacon_send(void)
 {
-	s64_t threshold;
+	static const struct bt_mesh_send_cb send_cb = {
+		.end = beacon_complete,
+	};
+	u32_t now = k_uptime_get_32();
 	int i;
 
 	BT_DBG("");
 
-	/* If the interval has passed or is within 5 seconds from now
-	 * send a beacon.
-	 */
-	threshold = k_uptime_get() + K_SECONDS(5);
-
 	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
 		struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
 		struct os_mbuf *buf;
+		u32_t time_diff;
 
 		if (sub->net_idx == BT_MESH_KEY_UNUSED) {
 			continue;
 		}
 
-		if (sub->beacon_sent + BEACON_INTERVAL(sub) > threshold) {
+		time_diff = now - sub->beacon_sent;
+		if (time_diff < K_SECONDS(600) &&
+		    time_diff < BEACON_THRESHOLD(sub)) {
 			continue;
 		}
 
@@ -142,11 +138,9 @@ static int secure_beacon_send(void)
 			return -ENOBUFS;
 		}
 
-		BT_MESH_ADV(buf)->user_data[0] = i;
-
 		bt_mesh_beacon_create(sub, buf);
 
-		bt_mesh_adv_send(buf, beacon_complete);
+		bt_mesh_adv_send(buf, &send_cb, sub);
 		net_buf_unref(buf);
 	}
 
@@ -173,7 +167,7 @@ static int unprovisioned_beacon_send(void)
 	/* OOB Info (2 bytes) + URI Hash (4 bytes) */
 	net_buf_add_zeros(buf, 2 + 4);
 
-	bt_mesh_adv_send(buf, NULL);
+	bt_mesh_adv_send(buf, NULL, NULL);
 	net_buf_unref(buf);
 
 #endif /* MYNEWT_VAL(BLE_MESH_PB_ADV) */
@@ -238,8 +232,8 @@ static void secure_beacon_recv(struct os_mbuf *buf)
 	u8_t *data, *net_id, *auth;
 	struct bt_mesh_subnet *sub;
 	u32_t iv_index;
+	bool new_key, kr_change, iv_change;
 	u8_t flags;
-	bool new_key;
 
 	if (buf->om_len < 21) {
 		BT_ERR("Too short secure beacon (len %u)", buf->om_len);
@@ -275,7 +269,7 @@ static void secure_beacon_recv(struct os_mbuf *buf)
 		return;
 	}
 
-	cache_add(data, sub->net_idx);
+	cache_add(data, sub);
 
 	/* If we have NetKey0 accept initiation only from it */
 	if (bt_mesh_subnet_get(BT_MESH_KEY_PRIMARY) &&
@@ -292,12 +286,21 @@ static void secure_beacon_recv(struct os_mbuf *buf)
 		bt_mesh_beacon_ivu_initiator(false);
 	}
 
-	bt_mesh_iv_update(iv_index, BT_MESH_IV_UPDATE(flags));
+	iv_change = bt_mesh_net_iv_update(iv_index, BT_MESH_IV_UPDATE(flags));
 
-	if (bt_mesh_kr_update(sub, BT_MESH_KEY_REFRESH(flags), new_key)) {
+	kr_change = bt_mesh_kr_update(sub, BT_MESH_KEY_REFRESH(flags), new_key);
+	if (kr_change) {
 		bt_mesh_net_beacon_update(sub);
 	}
 
+	if (iv_change) {
+		/* Update all subnets */
+		bt_mesh_net_sec_update(NULL);
+	} else if (kr_change) {
+		/* Key Refresh without IV Update only impacts one subnet */
+		bt_mesh_net_sec_update(sub);
+	}
+
 update_stats:
 	if (bt_mesh_beacon_get() == BT_MESH_BEACON_ENABLED &&
 	    sub->beacons_cur < 0xff) {
@@ -333,9 +336,6 @@ void bt_mesh_beacon_recv(struct os_mbuf *buf)
 void bt_mesh_beacon_init(void)
 {
 	k_delayed_work_init(&beacon_timer, beacon_send);
-
-	/* Start beaconing since we're unprovisioned */
-	k_work_submit(&beacon_timer.work);
 }
 
 void bt_mesh_beacon_ivu_initiator(bool enable)
diff --git a/net/nimble/host/mesh/src/beacon.h b/net/nimble/host/mesh/src/beacon.h
index 35e47989e..ac4bfed8a 100644
--- a/net/nimble/host/mesh/src/beacon.h
+++ b/net/nimble/host/mesh/src/beacon.h
@@ -11,21 +11,16 @@
 
 #include "os/os_mbuf.h"
 
-void
-bt_mesh_beacon_enable(void);
-void
-bt_mesh_beacon_disable(void);
+void bt_mesh_beacon_enable(void);
+void bt_mesh_beacon_disable(void);
 
-void
-bt_mesh_beacon_ivu_initiator(bool enable);
+void bt_mesh_beacon_ivu_initiator(bool enable);
 
-void
-bt_mesh_beacon_recv(struct os_mbuf *buf);
+void bt_mesh_beacon_recv(struct os_mbuf *buf);
 
-void
-bt_mesh_beacon_create(struct bt_mesh_subnet *sub, struct os_mbuf *buf);
+void bt_mesh_beacon_create(struct bt_mesh_subnet *sub,
+			   struct os_mbuf *buf);
 
-void
-bt_mesh_beacon_init(void);
+void bt_mesh_beacon_init(void);
 
 #endif
diff --git a/net/nimble/host/mesh/src/cfg_cli.c b/net/nimble/host/mesh/src/cfg_cli.c
new file mode 100644
index 000000000..2ec090549
--- /dev/null
+++ b/net/nimble/host/mesh/src/cfg_cli.c
@@ -0,0 +1,1399 @@
+/*  Bluetooth Mesh */
+
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "syscfg/syscfg.h"
+#if MYNEWT_VAL(BLE_MESH_CFG_CLI)
+
+#define BT_DBG_ENABLED (MYNEWT_VAL(BLE_MESH_DEBUG_MODEL))
+#include "mesh/mesh.h"
+
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include "foundation.h"
+
+#define CID_NVAL 0xffff
+
+struct comp_data {
+	u8_t *status;
+	struct os_mbuf *comp;
+};
+
+static s32_t msg_timeout = K_SECONDS(2);
+
+static struct bt_mesh_cfg_cli *cli;
+
+static void comp_data_status(struct bt_mesh_model *model,
+			     struct bt_mesh_msg_ctx *ctx,
+			     struct os_mbuf *buf)
+{
+	struct comp_data *param;
+	size_t to_copy;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (cli->op_pending != OP_DEV_COMP_DATA_STATUS) {
+		BT_WARN("Unexpected Composition Data Status");
+		return;
+	}
+
+	param = cli->op_param;
+
+	*(param->status) = net_buf_simple_pull_u8(buf);
+	to_copy  = min(net_buf_simple_tailroom(param->comp), buf->om_len);
+	net_buf_simple_add_mem(param->comp, buf->om_data, to_copy);
+
+	k_sem_give(&cli->op_sync);
+}
+
+static void state_status_u8(struct bt_mesh_model *model,
+			    struct bt_mesh_msg_ctx *ctx,
+			    struct os_mbuf*buf,
+			    u32_t expect_status)
+{
+	u8_t *status;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (cli->op_pending != expect_status) {
+		BT_WARN("Unexpected Status (0x%08x != 0x%08x)",
+			cli->op_pending, expect_status);
+		return;
+	}
+
+	status = cli->op_param;
+	*status = net_buf_simple_pull_u8(buf);
+
+	k_sem_give(&cli->op_sync);
+}
+
+static void beacon_status(struct bt_mesh_model *model,
+			  struct bt_mesh_msg_ctx *ctx,
+			  struct os_mbuf *buf)
+{
+	state_status_u8(model, ctx, buf, OP_BEACON_STATUS);
+}
+
+static void ttl_status(struct bt_mesh_model *model,
+			  struct bt_mesh_msg_ctx *ctx,
+			  struct os_mbuf*buf)
+{
+	state_status_u8(model, ctx, buf, OP_DEFAULT_TTL_STATUS);
+}
+
+static void friend_status(struct bt_mesh_model *model,
+			  struct bt_mesh_msg_ctx *ctx,
+			  struct os_mbuf*buf)
+{
+	state_status_u8(model, ctx, buf, OP_FRIEND_STATUS);
+}
+
+static void gatt_proxy_status(struct bt_mesh_model *model,
+			      struct bt_mesh_msg_ctx *ctx,
+			      struct os_mbuf*buf)
+{
+	state_status_u8(model, ctx, buf, OP_GATT_PROXY_STATUS);
+}
+
+struct relay_param {
+	u8_t *status;
+	u8_t *transmit;
+};
+
+static void relay_status(struct bt_mesh_model *model,
+			 struct bt_mesh_msg_ctx *ctx,
+			 struct os_mbuf*buf)
+{
+	struct relay_param *param;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (cli->op_pending != OP_RELAY_STATUS) {
+		BT_WARN("Unexpected Relay Status message");
+		return;
+	}
+
+	param = cli->op_param;
+	*param->status = net_buf_simple_pull_u8(buf);
+	*param->transmit = net_buf_simple_pull_u8(buf);
+
+	k_sem_give(&cli->op_sync);
+}
+
+struct net_key_param {
+	u8_t *status;
+	u16_t net_idx;
+};
+
+static void net_key_status(struct bt_mesh_model *model,
+			   struct bt_mesh_msg_ctx *ctx,
+			   struct os_mbuf *buf)
+{
+	struct net_key_param *param;
+	u16_t net_idx, app_idx;
+	u8_t status;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (cli->op_pending != OP_NET_KEY_STATUS) {
+		BT_WARN("Unexpected Net Key Status message");
+		return;
+	}
+
+	status = net_buf_simple_pull_u8(buf);
+	key_idx_unpack(buf, &net_idx, &app_idx);
+
+	param = cli->op_param;
+	if (param->net_idx != net_idx) {
+		BT_WARN("Net Key Status key index does not match");
+		return;
+	}
+
+	*param->status = status;
+
+	k_sem_give(&cli->op_sync);
+}
+
+struct app_key_param {
+	u8_t *status;
+	u16_t net_idx;
+	u16_t app_idx;
+};
+
+static void app_key_status(struct bt_mesh_model *model,
+			   struct bt_mesh_msg_ctx *ctx,
+			   struct os_mbuf*buf)
+{
+	struct app_key_param *param;
+	u16_t net_idx, app_idx;
+	u8_t status;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (cli->op_pending != OP_APP_KEY_STATUS) {
+		BT_WARN("Unexpected App Key Status message");
+		return;
+	}
+
+	status = net_buf_simple_pull_u8(buf);
+	key_idx_unpack(buf, &net_idx, &app_idx);
+
+	param = cli->op_param;
+	if (param->net_idx != net_idx || param->app_idx != app_idx) {
+		BT_WARN("App Key Status key indices did not match");
+		return;
+	}
+
+	*param->status = status;
+
+	k_sem_give(&cli->op_sync);
+}
+
+struct mod_app_param {
+	u8_t *status;
+	u16_t elem_addr;
+	u16_t mod_app_idx;
+	u16_t mod_id;
+	u16_t cid;
+};
+
+static void mod_app_status(struct bt_mesh_model *model,
+			   struct bt_mesh_msg_ctx *ctx,
+			   struct os_mbuf*buf)
+{
+	u16_t elem_addr, mod_app_idx, mod_id, cid;
+	struct mod_app_param *param;
+	u8_t status;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (cli->op_pending != OP_MOD_APP_STATUS) {
+		BT_WARN("Unexpected Model App Status message");
+		return;
+	}
+
+	status = net_buf_simple_pull_u8(buf);
+	elem_addr = net_buf_simple_pull_le16(buf);
+	mod_app_idx = net_buf_simple_pull_le16(buf);
+
+	if (buf->om_len >= 4) {
+		cid = net_buf_simple_pull_le16(buf);
+	} else {
+		cid = CID_NVAL;
+	}
+
+	mod_id = net_buf_simple_pull_le16(buf);
+
+	param = cli->op_param;
+	if (param->elem_addr != elem_addr ||
+	    param->mod_app_idx != mod_app_idx || param->mod_id != mod_id ||
+	    param->cid != cid) {
+		BT_WARN("Model App Status parameters did not match");
+		return;
+	}
+
+	*param->status = status;
+
+	k_sem_give(&cli->op_sync);
+}
+
+struct mod_pub_param {
+	u16_t                       mod_id;
+	u16_t                       cid;
+	u16_t                       elem_addr;
+	u8_t                       *status;
+	struct bt_mesh_cfg_mod_pub *pub;
+};
+
+static void mod_pub_status(struct bt_mesh_model *model,
+			   struct bt_mesh_msg_ctx *ctx,
+			   struct os_mbuf*buf)
+{
+	u16_t mod_id, cid, elem_addr;
+	struct mod_pub_param *param;
+	u8_t status;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (cli->op_pending != OP_MOD_PUB_STATUS) {
+		BT_WARN("Unexpected Model Pub Status message");
+		return;
+	}
+
+	param = cli->op_param;
+	if (param->cid != CID_NVAL) {
+		if (buf->om_len < 14) {
+			BT_WARN("Unexpected Mod Pub Status with SIG Model");
+			return;
+		}
+
+		cid = sys_get_le16(&buf->om_data[10]);
+		mod_id = sys_get_le16(&buf->om_data[12]);
+	} else {
+		if (buf->om_len > 12) {
+			BT_WARN("Unexpected Mod Pub Status with Vendor Model");
+			return;
+		}
+
+		cid = CID_NVAL;
+		mod_id = sys_get_le16(&buf->om_data[10]);
+	}
+
+	if (mod_id != param->mod_id || cid != param->cid) {
+		BT_WARN("Mod Pub Model ID or Company ID mismatch");
+		return;
+	}
+
+	status = net_buf_simple_pull_u8(buf);
+
+	elem_addr = net_buf_simple_pull_le16(buf);
+	if (elem_addr != param->elem_addr) {
+		BT_WARN("Model Pub Status for unexpected element (0x%04x)",
+			elem_addr);
+		return;
+	}
+
+	*param->status = status;
+
+	if (param->pub) {
+		param->pub->addr = net_buf_simple_pull_le16(buf);
+		param->pub->app_idx = net_buf_simple_pull_le16(buf);
+		param->pub->cred_flag = (param->pub->app_idx & BIT(12));
+		param->pub->app_idx &= BIT_MASK(12);
+		param->pub->ttl = net_buf_simple_pull_u8(buf);
+		param->pub->period = net_buf_simple_pull_u8(buf);
+		param->pub->transmit = net_buf_simple_pull_u8(buf);
+	}
+
+	k_sem_give(&cli->op_sync);
+}
+
+struct mod_sub_param {
+	u8_t *status;
+	u16_t elem_addr;
+	u16_t *sub_addr;
+	u16_t *expect_sub;
+	u16_t mod_id;
+	u16_t cid;
+};
+
+static void mod_sub_status(struct bt_mesh_model *model,
+			   struct bt_mesh_msg_ctx *ctx,
+			   struct os_mbuf*buf)
+{
+	u16_t elem_addr, sub_addr, mod_id, cid;
+	struct mod_sub_param *param;
+	u8_t status;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (cli->op_pending != OP_MOD_SUB_STATUS) {
+		BT_WARN("Unexpected Model Subscription Status message");
+		return;
+	}
+
+	status = net_buf_simple_pull_u8(buf);
+	elem_addr = net_buf_simple_pull_le16(buf);
+	sub_addr = net_buf_simple_pull_le16(buf);
+
+	if (buf->om_len >= 4) {
+		cid = net_buf_simple_pull_le16(buf);
+	} else {
+		cid = CID_NVAL;
+	}
+
+	mod_id = net_buf_simple_pull_le16(buf);
+
+	param = cli->op_param;
+	if (param->elem_addr != elem_addr || param->mod_id != mod_id ||
+	    (param->expect_sub && *param->expect_sub != sub_addr) ||
+	    param->cid != cid) {
+		BT_WARN("Model Subscription Status parameters did not match");
+		return;
+	}
+
+	if (param->sub_addr) {
+		*param->sub_addr = sub_addr;
+	}
+
+	*param->status = status;
+
+	k_sem_give(&cli->op_sync);
+}
+
+struct hb_sub_param {
+	u8_t *status;
+	struct bt_mesh_cfg_hb_sub *sub;
+};
+
+static void hb_sub_status(struct bt_mesh_model *model,
+			  struct bt_mesh_msg_ctx *ctx,
+			  struct os_mbuf*buf)
+{
+	struct hb_sub_param *param;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (cli->op_pending != OP_HEARTBEAT_SUB_STATUS) {
+		BT_WARN("Unexpected Heartbeat Subscription Status message");
+		return;
+	}
+
+	param = cli->op_param;
+
+	*param->status = net_buf_simple_pull_u8(buf);
+
+	param->sub->src = net_buf_simple_pull_le16(buf);
+	param->sub->dst = net_buf_simple_pull_le16(buf);
+	param->sub->period = net_buf_simple_pull_u8(buf);
+	param->sub->count = net_buf_simple_pull_u8(buf);
+	param->sub->min = net_buf_simple_pull_u8(buf);
+	param->sub->max = net_buf_simple_pull_u8(buf);
+
+	k_sem_give(&cli->op_sync);
+}
+
+struct hb_pub_param {
+	u8_t *status;
+	struct bt_mesh_cfg_hb_pub *pub;
+};
+
+static void hb_pub_status(struct bt_mesh_model *model,
+			  struct bt_mesh_msg_ctx *ctx,
+			  struct os_mbuf *buf)
+{
+	struct hb_pub_param *param;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (cli->op_pending != OP_HEARTBEAT_PUB_STATUS) {
+		BT_WARN("Unexpected Heartbeat Publication Status message");
+		return;
+	}
+
+	param = cli->op_param;
+
+	*param->status = net_buf_simple_pull_u8(buf);
+
+	if (param->pub) {
+		param->pub->dst = net_buf_simple_pull_le16(buf);
+		param->pub->count = net_buf_simple_pull_u8(buf);
+		param->pub->period = net_buf_simple_pull_u8(buf);
+		param->pub->ttl = net_buf_simple_pull_u8(buf);
+		param->pub->feat = net_buf_simple_pull_u8(buf);
+		param->pub->net_idx = net_buf_simple_pull_u8(buf);
+	}
+
+	k_sem_give(&cli->op_sync);
+}
+
+const struct bt_mesh_model_op bt_mesh_cfg_cli_op[] = {
+	{ OP_DEV_COMP_DATA_STATUS,   15,  comp_data_status },
+	{ OP_BEACON_STATUS,          1,   beacon_status },
+	{ OP_DEFAULT_TTL_STATUS,     1,   ttl_status },
+	{ OP_FRIEND_STATUS,          1,   friend_status },
+	{ OP_GATT_PROXY_STATUS,      1,   gatt_proxy_status },
+	{ OP_RELAY_STATUS,           2,   relay_status },
+	{ OP_NET_KEY_STATUS,         3,   net_key_status },
+	{ OP_APP_KEY_STATUS,         4,   app_key_status },
+	{ OP_MOD_APP_STATUS,         7,   mod_app_status },
+	{ OP_MOD_PUB_STATUS,         12,  mod_pub_status },
+	{ OP_MOD_SUB_STATUS,         7,   mod_sub_status },
+	{ OP_HEARTBEAT_SUB_STATUS,   9,   hb_sub_status },
+	{ OP_HEARTBEAT_PUB_STATUS,   10,  hb_pub_status },
+	BT_MESH_MODEL_OP_END,
+};
+
+static int check_cli(void)
+{
+	if (!cli) {
+		BT_ERR("No available Configuration Client context!");
+		return -EINVAL;
+	}
+
+	if (cli->op_pending) {
+		BT_WARN("Another synchronous operation pending");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static int cli_wait(void *param, u32_t op)
+{
+	int err;
+
+	cli->op_param = param;
+	cli->op_pending = op;
+
+	err = k_sem_take(&cli->op_sync, msg_timeout);
+
+	cli->op_pending = 0;
+	cli->op_param = NULL;
+
+	return err;
+}
+
+int bt_mesh_cfg_comp_data_get(u16_t net_idx, u16_t addr, u8_t page,
+			      u8_t *status, struct os_mbuf *comp)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 1 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct comp_data param = {
+		.status = status,
+		.comp = comp,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_DEV_COMP_DATA_GET);
+	net_buf_simple_add_u8(msg, page);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	return cli_wait(&param, OP_DEV_COMP_DATA_STATUS);
+}
+
+static int get_state_u8(u16_t net_idx, u16_t addr, u32_t op, u32_t rsp,
+			u8_t *val)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 0 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, op);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	return cli_wait(val, rsp);
+}
+
+static int set_state_u8(u16_t net_idx, u16_t addr, u32_t op, u32_t rsp,
+			u8_t new_val, u8_t *val)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 1 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, op);
+	net_buf_simple_add_u8(msg, new_val);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	return cli_wait(val, rsp);
+}
+
+int bt_mesh_cfg_beacon_get(u16_t net_idx, u16_t addr, u8_t *status)
+{
+	return get_state_u8(net_idx, addr, OP_BEACON_GET, OP_BEACON_STATUS,
+			    status);
+}
+
+int bt_mesh_cfg_beacon_set(u16_t net_idx, u16_t addr, u8_t val, u8_t *status)
+{
+	return set_state_u8(net_idx, addr, OP_BEACON_SET, OP_BEACON_STATUS,
+			    val, status);
+}
+
+int bt_mesh_cfg_ttl_get(u16_t net_idx, u16_t addr, u8_t *ttl)
+{
+	return get_state_u8(net_idx, addr, OP_DEFAULT_TTL_GET,
+			    OP_DEFAULT_TTL_STATUS, ttl);
+}
+
+int bt_mesh_cfg_ttl_set(u16_t net_idx, u16_t addr, u8_t val, u8_t *ttl)
+{
+	return set_state_u8(net_idx, addr, OP_DEFAULT_TTL_SET,
+			    OP_DEFAULT_TTL_STATUS, val, ttl);
+}
+
+int bt_mesh_cfg_friend_get(u16_t net_idx, u16_t addr, u8_t *status)
+{
+	return get_state_u8(net_idx, addr, OP_FRIEND_GET,
+			    OP_FRIEND_STATUS, status);
+}
+
+int bt_mesh_cfg_friend_set(u16_t net_idx, u16_t addr, u8_t val, u8_t *status)
+{
+	return set_state_u8(net_idx, addr, OP_FRIEND_SET, OP_FRIEND_STATUS,
+			    val, status);
+}
+
+int bt_mesh_cfg_gatt_proxy_get(u16_t net_idx, u16_t addr, u8_t *status)
+{
+	return get_state_u8(net_idx, addr, OP_GATT_PROXY_GET,
+			    OP_GATT_PROXY_STATUS, status);
+}
+
+int bt_mesh_cfg_gatt_proxy_set(u16_t net_idx, u16_t addr, u8_t val,
+			       u8_t *status)
+{
+	return set_state_u8(net_idx, addr, OP_GATT_PROXY_SET,
+			    OP_GATT_PROXY_STATUS, val, status);
+}
+
+int bt_mesh_cfg_relay_get(u16_t net_idx, u16_t addr, u8_t *status,
+			  u8_t *transmit)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(2 + 0 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct relay_param param = {
+		.status = status,
+		.transmit = transmit,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_RELAY_GET);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	return cli_wait(&param, OP_RELAY_STATUS);
+}
+
+int bt_mesh_cfg_relay_set(u16_t net_idx, u16_t addr, u8_t new_relay,
+			  u8_t new_transmit, u8_t *status, u8_t *transmit)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(2 + 2 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct relay_param param = {
+		.status = status,
+		.transmit = transmit,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_RELAY_SET);
+	net_buf_simple_add_u8(msg, new_relay);
+	net_buf_simple_add_u8(msg, new_transmit);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	return cli_wait(&param, OP_RELAY_STATUS);
+}
+
+int bt_mesh_cfg_net_key_add(u16_t net_idx, u16_t addr, u16_t key_net_idx,
+			    const u8_t net_key[16], u8_t *status)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 18 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct net_key_param param = {
+		.status = status,
+		.net_idx = key_net_idx,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_NET_KEY_ADD);
+	net_buf_simple_add_le16(msg, key_net_idx);
+	net_buf_simple_add_mem(msg, net_key, 16);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_NET_KEY_STATUS);
+}
+
+int bt_mesh_cfg_app_key_add(u16_t net_idx, u16_t addr, u16_t key_net_idx,
+			    u16_t key_app_idx, const u8_t app_key[16],
+			    u8_t *status)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(1 + 19 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct app_key_param param = {
+		.status = status,
+		.net_idx = key_net_idx,
+		.app_idx = key_app_idx,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_APP_KEY_ADD);
+	key_idx_pack(msg, key_net_idx, key_app_idx);
+	net_buf_simple_add_mem(msg, app_key, 16);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_APP_KEY_STATUS);
+}
+
+static int mod_app_bind(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			u16_t mod_app_idx, u16_t mod_id, u16_t cid,
+			u8_t *status)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(2 + 8 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct mod_app_param param = {
+		.status = status,
+		.elem_addr = elem_addr,
+		.mod_app_idx = mod_app_idx,
+		.mod_id = mod_id,
+		.cid = cid,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_MOD_APP_BIND);
+	net_buf_simple_add_le16(msg, elem_addr);
+	net_buf_simple_add_le16(msg, mod_app_idx);
+
+	if (cid != CID_NVAL) {
+		net_buf_simple_add_le16(msg, cid);
+	}
+
+	net_buf_simple_add_le16(msg, mod_id);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_MOD_APP_STATUS);
+}
+
+int bt_mesh_cfg_mod_app_bind(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			     u16_t mod_app_idx, u16_t mod_id, u8_t *status)
+{
+	return mod_app_bind(net_idx, addr, elem_addr, mod_app_idx, mod_id,
+			    CID_NVAL, status);
+}
+
+int bt_mesh_cfg_mod_app_bind_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				 u16_t mod_app_idx, u16_t mod_id, u16_t cid,
+				 u8_t *status)
+{
+	if (cid == CID_NVAL) {
+		return -EINVAL;
+	}
+
+	return mod_app_bind(net_idx, addr, elem_addr, mod_app_idx, mod_id, cid,
+			    status);
+}
+
+static int mod_sub(u32_t op, u16_t net_idx, u16_t addr, u16_t elem_addr,
+		   u16_t sub_addr, u16_t mod_id, u16_t cid, u8_t *status)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(2 + 8 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct mod_sub_param param = {
+		.status = status,
+		.elem_addr = elem_addr,
+		.expect_sub = &sub_addr,
+		.mod_id = mod_id,
+		.cid = cid,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, op);
+	net_buf_simple_add_le16(msg, elem_addr);
+	net_buf_simple_add_le16(msg, sub_addr);
+
+	if (cid != CID_NVAL) {
+		net_buf_simple_add_le16(msg, cid);
+	}
+
+	net_buf_simple_add_le16(msg, mod_id);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_MOD_SUB_STATUS);
+}
+
+int bt_mesh_cfg_mod_sub_add(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			    u16_t sub_addr, u16_t mod_id, u8_t *status)
+{
+	return mod_sub(OP_MOD_SUB_ADD, net_idx, addr, elem_addr, sub_addr,
+		       mod_id, CID_NVAL, status);
+}
+
+int bt_mesh_cfg_mod_sub_add_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				 u16_t sub_addr, u16_t mod_id, u16_t cid,
+				 u8_t *status)
+{
+	if (cid == CID_NVAL) {
+		return -EINVAL;
+	}
+
+	return mod_sub(OP_MOD_SUB_ADD, net_idx, addr, elem_addr, sub_addr,
+		       mod_id, cid, status);
+}
+
+int bt_mesh_cfg_mod_sub_del(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			    u16_t sub_addr, u16_t mod_id, u8_t *status)
+{
+	return mod_sub(OP_MOD_SUB_DEL, net_idx, addr, elem_addr, sub_addr,
+		       mod_id, CID_NVAL, status);
+}
+
+int bt_mesh_cfg_mod_sub_del_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				 u16_t sub_addr, u16_t mod_id, u16_t cid,
+				 u8_t *status)
+{
+	if (cid == CID_NVAL) {
+		return -EINVAL;
+	}
+
+	return mod_sub(OP_MOD_SUB_DEL, net_idx, addr, elem_addr, sub_addr,
+		       mod_id, cid, status);
+}
+
+int bt_mesh_cfg_mod_sub_overwrite(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				  u16_t sub_addr, u16_t mod_id, u8_t *status)
+{
+	return mod_sub(OP_MOD_SUB_OVERWRITE, net_idx, addr, elem_addr,
+		       sub_addr, mod_id, CID_NVAL, status);
+}
+
+int bt_mesh_cfg_mod_sub_overwrite_vnd(u16_t net_idx, u16_t addr,
+				      u16_t elem_addr, u16_t sub_addr,
+				      u16_t mod_id, u16_t cid, u8_t *status)
+{
+	if (cid == CID_NVAL) {
+		return -EINVAL;
+	}
+
+	return mod_sub(OP_MOD_SUB_OVERWRITE, net_idx, addr, elem_addr,
+		       sub_addr, mod_id, cid, status);
+}
+
+static int mod_sub_va(u32_t op, u16_t net_idx, u16_t addr, u16_t elem_addr,
+		      const u8_t label[16], u16_t mod_id, u16_t cid,
+		      u16_t *virt_addr, u8_t *status)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 22 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct mod_sub_param param = {
+		.status = status,
+		.elem_addr = elem_addr,
+		.sub_addr = virt_addr,
+		.mod_id = mod_id,
+		.cid = cid,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	BT_DBG("net_idx 0x%04x addr 0x%04x elem_addr 0x%04x label %s",
+	       net_idx, addr, elem_addr, label);
+	BT_DBG("mod_id 0x%04x cid 0x%04x", mod_id, cid);
+
+	bt_mesh_model_msg_init(msg, op);
+	net_buf_simple_add_le16(msg, elem_addr);
+	net_buf_simple_add_mem(msg, label, 16);
+
+	if (cid != CID_NVAL) {
+		net_buf_simple_add_le16(msg, cid);
+	}
+
+	net_buf_simple_add_le16(msg, mod_id);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_MOD_SUB_STATUS);
+}
+
+int bt_mesh_cfg_mod_sub_va_add(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			       const u8_t label[16], u16_t mod_id,
+			       u16_t *virt_addr, u8_t *status)
+{
+	return mod_sub_va(OP_MOD_SUB_VA_ADD, net_idx, addr, elem_addr, label,
+			  mod_id, CID_NVAL, virt_addr, status);
+}
+
+int bt_mesh_cfg_mod_sub_va_add_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				   const u8_t label[16], u16_t mod_id,
+				   u16_t cid, u16_t *virt_addr, u8_t *status)
+{
+	if (cid == CID_NVAL) {
+		return -EINVAL;
+	}
+
+	return mod_sub_va(OP_MOD_SUB_VA_ADD, net_idx, addr, elem_addr, label,
+			  mod_id, cid, virt_addr, status);
+}
+
+int bt_mesh_cfg_mod_sub_va_del(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			       const u8_t label[16], u16_t mod_id,
+			       u16_t *virt_addr, u8_t *status)
+{
+	return mod_sub_va(OP_MOD_SUB_VA_DEL, net_idx, addr, elem_addr, label,
+			  mod_id, CID_NVAL, virt_addr, status);
+}
+
+int bt_mesh_cfg_mod_sub_va_del_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				   const u8_t label[16], u16_t mod_id,
+				   u16_t cid, u16_t *virt_addr, u8_t *status)
+{
+	if (cid == CID_NVAL) {
+		return -EINVAL;
+	}
+
+	return mod_sub_va(OP_MOD_SUB_VA_DEL, net_idx, addr, elem_addr, label,
+			  mod_id, cid, virt_addr, status);
+}
+
+int bt_mesh_cfg_mod_sub_va_overwrite(u16_t net_idx, u16_t addr,
+				     u16_t elem_addr, const u8_t label[16],
+				     u16_t mod_id, u16_t *virt_addr,
+				     u8_t *status)
+{
+	return mod_sub_va(OP_MOD_SUB_VA_OVERWRITE, net_idx, addr, elem_addr,
+			  label, mod_id, CID_NVAL, virt_addr, status);
+}
+
+int bt_mesh_cfg_mod_sub_va_overwrite_vnd(u16_t net_idx, u16_t addr,
+					 u16_t elem_addr, const u8_t label[16],
+					 u16_t mod_id, u16_t cid,
+					 u16_t *virt_addr, u8_t *status)
+{
+	if (cid == CID_NVAL) {
+		return -EINVAL;
+	}
+
+	return mod_sub_va(OP_MOD_SUB_VA_OVERWRITE, net_idx, addr, elem_addr,
+			  label, mod_id, cid, virt_addr, status);
+}
+
+static int mod_pub_get(u16_t net_idx, u16_t addr, u16_t elem_addr,
+		       u16_t mod_id, u16_t cid,
+		       struct bt_mesh_cfg_mod_pub *pub, u8_t *status)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(2 + 6 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct mod_pub_param param = {
+		.mod_id = mod_id,
+		.cid = cid,
+		.elem_addr = elem_addr,
+		.status = status,
+		.pub = pub,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_MOD_PUB_GET);
+
+	net_buf_simple_add_le16(msg, elem_addr);
+
+	if (cid != CID_NVAL) {
+		net_buf_simple_add_le16(msg, cid);
+	}
+
+	net_buf_simple_add_le16(msg, mod_id);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_MOD_PUB_STATUS);
+}
+
+int bt_mesh_cfg_mod_pub_get(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			    u16_t mod_id, struct bt_mesh_cfg_mod_pub *pub,
+			    u8_t *status)
+{
+	return mod_pub_get(net_idx, addr, elem_addr, mod_id, CID_NVAL,
+			   pub, status);
+}
+
+int bt_mesh_cfg_mod_pub_get_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				u16_t mod_id, u16_t cid,
+				struct bt_mesh_cfg_mod_pub *pub, u8_t *status)
+{
+	if (cid == CID_NVAL) {
+		return -EINVAL;
+	}
+
+	return mod_pub_get(net_idx, addr, elem_addr, mod_id, CID_NVAL,
+			   pub, status);
+}
+
+static int mod_pub_set(u16_t net_idx, u16_t addr, u16_t elem_addr,
+		       u16_t mod_id, u16_t cid,
+		       struct bt_mesh_cfg_mod_pub *pub, u8_t *status)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(2 + 13 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct mod_pub_param param = {
+		.mod_id = mod_id,
+		.cid = cid,
+		.elem_addr = elem_addr,
+		.status = status,
+		.pub = pub,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_MOD_PUB_SET);
+
+	net_buf_simple_add_le16(msg, elem_addr);
+	net_buf_simple_add_le16(msg, pub->addr);
+	net_buf_simple_add_le16(msg, (pub->app_idx & (pub->cred_flag << 12)));
+	net_buf_simple_add_u8(msg, pub->ttl);
+	net_buf_simple_add_u8(msg, pub->period);
+	net_buf_simple_add_u8(msg, pub->transmit);
+
+	if (cid != CID_NVAL) {
+		net_buf_simple_add_le16(msg, cid);
+	}
+
+	net_buf_simple_add_le16(msg, mod_id);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_MOD_PUB_STATUS);
+}
+
+int bt_mesh_cfg_mod_pub_set(u16_t net_idx, u16_t addr, u16_t elem_addr,
+			    u16_t mod_id, struct bt_mesh_cfg_mod_pub *pub,
+			    u8_t *status)
+{
+	return mod_pub_set(net_idx, addr, elem_addr, mod_id, CID_NVAL,
+			   pub, status);
+}
+
+int bt_mesh_cfg_mod_pub_set_vnd(u16_t net_idx, u16_t addr, u16_t elem_addr,
+				u16_t mod_id, u16_t cid,
+				struct bt_mesh_cfg_mod_pub *pub, u8_t *status)
+{
+	if (cid == CID_NVAL) {
+		return -EINVAL;
+	}
+
+	return mod_pub_set(net_idx, addr, elem_addr, mod_id, CID_NVAL,
+			   pub, status);
+}
+
+int bt_mesh_cfg_hb_sub_set(u16_t net_idx, u16_t addr,
+			   struct bt_mesh_cfg_hb_sub *sub, u8_t *status)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(2 + 5 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct hb_sub_param param = {
+		.status = status,
+		.sub = sub,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_HEARTBEAT_SUB_SET);
+	net_buf_simple_add_le16(msg, sub->src);
+	net_buf_simple_add_le16(msg, sub->dst);
+	net_buf_simple_add_u8(msg, sub->period);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_HEARTBEAT_SUB_STATUS);
+}
+
+int bt_mesh_cfg_hb_sub_get(u16_t net_idx, u16_t addr,
+			   struct bt_mesh_cfg_hb_sub *sub, u8_t *status)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(2 + 0 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct hb_sub_param param = {
+		.status = status,
+		.sub = sub,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_HEARTBEAT_SUB_GET);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_HEARTBEAT_SUB_STATUS);
+}
+
+int bt_mesh_cfg_hb_pub_set(u16_t net_idx, u16_t addr,
+			   const struct bt_mesh_cfg_hb_pub *pub, u8_t *status)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(2 + 9 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct hb_pub_param param = {
+		.status = status,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_HEARTBEAT_PUB_SET);
+	net_buf_simple_add_le16(msg, pub->dst);
+	net_buf_simple_add_u8(msg, pub->count);
+	net_buf_simple_add_u8(msg, pub->period);
+	net_buf_simple_add_u8(msg, pub->ttl);
+	net_buf_simple_add_le16(msg, pub->feat);
+	net_buf_simple_add_le16(msg, pub->net_idx);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_HEARTBEAT_PUB_STATUS);
+}
+
+int bt_mesh_cfg_hb_pub_get(u16_t net_idx, u16_t addr,
+			   struct bt_mesh_cfg_hb_pub *pub, u8_t *status)
+{
+	struct os_mbuf*msg = NET_BUF_SIMPLE(2 + 0 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = BT_MESH_KEY_DEV,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct hb_pub_param param = {
+		.status = status,
+		.pub = pub,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_HEARTBEAT_PUB_GET);
+
+	err = bt_mesh_model_send(cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!status) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_HEARTBEAT_PUB_STATUS);
+}
+
+s32_t bt_mesh_cfg_cli_timeout_get(void)
+{
+	return msg_timeout;
+}
+
+void bt_mesh_cfg_cli_timeout_set(s32_t timeout)
+{
+	msg_timeout = timeout;
+}
+
+int bt_mesh_cfg_cli_init(struct bt_mesh_model *model, bool primary)
+{
+	BT_DBG("primary %u", primary);
+
+	if (!primary) {
+		BT_ERR("Configuration Client only allowed in primary element");
+		return -EINVAL;
+	}
+
+	if (!model->user_data) {
+		BT_ERR("No Configuration Client context provided");
+		return -EINVAL;
+	}
+
+	cli = model->user_data;
+	cli->model = model;
+
+	/* Configuration Model security is device-key based */
+	model->keys[0] = BT_MESH_KEY_DEV;
+
+	k_sem_init(&cli->op_sync, 0, 1);
+
+	return 0;
+}
+
+#endif
diff --git a/net/nimble/host/mesh/src/cfg.c b/net/nimble/host/mesh/src/cfg_srv.c
similarity index 92%
rename from net/nimble/host/mesh/src/cfg.c
rename to net/nimble/host/mesh/src/cfg_srv.c
index 5c6779908..b86fba758 100644
--- a/net/nimble/host/mesh/src/cfg.c
+++ b/net/nimble/host/mesh/src/cfg_srv.c
@@ -26,35 +26,21 @@
 #include "beacon.h"
 #include "proxy.h"
 #include "foundation.h"
+#include "friend.h"
 
 #define DEFAULT_TTL 7
 
-static struct bt_mesh_cfg *conf;
+static struct bt_mesh_cfg_srv *conf;
 
 static struct label {
 	u16_t addr;
 	u8_t  uuid[16];
 } labels[MYNEWT_VAL(BLE_MESH_LABEL_COUNT)];
 
-static inline void key_idx_unpack(struct os_mbuf *buf,
-				  u16_t *idx1, u16_t *idx2)
-{
-	*idx1 = sys_get_le16(&buf->om_data[0]) & 0xfff;
-	*idx2 = sys_get_le16(&buf->om_data[1]) >> 4;
-	net_buf_simple_pull(buf, 3);
-}
-
-static inline void key_idx_pack(struct os_mbuf *buf,
-				u16_t idx1, u16_t idx2)
-{
-	net_buf_simple_add_le16(buf, idx1 | ((idx2 & 0x00f) << 12));
-	net_buf_simple_add_u8(buf, idx2 >> 4);
-}
-
 static void hb_send(struct bt_mesh_model *model)
 {
 
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 	u16_t feat = 0;
 	struct __packed {
 		u8_t  init_ttl;
@@ -70,6 +56,7 @@ static void hb_send(struct bt_mesh_model *model)
 		.sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx),
 		.ctx = &ctx,
 		.src = model->elem->addr,
+		.xmit = bt_mesh_net_transmit_get(),
 	};
 
 	hb.init_ttl = cfg->hb_pub.ttl;
@@ -96,7 +83,8 @@ static void hb_send(struct bt_mesh_model *model)
 
 	BT_DBG("InitTTL %u feat 0x%04x", cfg->hb_pub.ttl, feat);
 
-	bt_mesh_ctl_send(&tx, TRANS_CTL_OP_HEARTBEAT, &hb, sizeof(hb), NULL);
+	bt_mesh_ctl_send(&tx, TRANS_CTL_OP_HEARTBEAT, &hb, sizeof(hb),
+			 NULL, NULL, NULL);
 }
 
 static int comp_add_elem(struct os_mbuf *buf, struct bt_mesh_elem *elem,
@@ -261,7 +249,7 @@ static u8_t _mod_pub_set(struct bt_mesh_model *model, u16_t pub_addr,
 		return STATUS_FEAT_NOT_SUPP;
 	}
 
-	if (!model->pub->func && period) {
+	if (!model->pub->update && period) {
 		return STATUS_NVAL_PUB_PARAM;
 	}
 
@@ -272,8 +260,9 @@ static u8_t _mod_pub_set(struct bt_mesh_model *model, u16_t pub_addr,
 		model->pub->ttl = 0;
 		model->pub->period = 0;
 		model->pub->retransmit = 0;
+		model->pub->count = 0;
 
-		if (model->pub->func) {
+		if (model->pub->update) {
 			k_delayed_work_cancel(&model->pub->timer);
 		}
 
@@ -291,7 +280,7 @@ static u8_t _mod_pub_set(struct bt_mesh_model *model, u16_t pub_addr,
 	model->pub->period = period;
 	model->pub->retransmit = retransmit;
 
-	if (model->pub->func) {
+	if (model->pub->update) {
 		s32_t period_ms;
 
 		period_ms = bt_mesh_model_pub_period_get(model);
@@ -353,7 +342,7 @@ static u8_t mod_unbind(struct bt_mesh_model *model, u16_t key_idx)
 
 		if (model->pub && model->pub->key == key_idx) {
 			_mod_pub_set(model, BT_MESH_ADDR_UNASSIGNED,
-				     BT_MESH_KEY_UNUSED, 0, 0, 0, 0);
+				     0, 0, 0, 0, 0);
 		}
 
 		return STATUS_SUCCESS;
@@ -672,7 +661,7 @@ static void beacon_set(struct bt_mesh_model *model,
 {
 	/* Needed size: opcode (2 bytes) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 1 + 4);
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 
 	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
 	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
@@ -734,7 +723,7 @@ static void default_ttl_set(struct bt_mesh_model *model,
 {
 	/* Needed size: opcode (2 bytes) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 1 + 4);
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 
 	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
 	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
@@ -742,7 +731,7 @@ 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] <= 0x7f && buf->om_data[0] != 0x01) {
+	} else if (buf->om_data[0] <= BT_MESH_TTL_MAX && buf->om_data[0] != 0x01) {
 		cfg->default_ttl = buf->om_data[0];
 	} else {
 		BT_WARN("Prohibited Default TTL value 0x%02x", buf->om_data[0]);
@@ -792,7 +781,7 @@ static void gatt_proxy_set(struct bt_mesh_model *model,
 			   struct bt_mesh_msg_ctx *ctx,
 			   struct os_mbuf *buf)
 {
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 	struct bt_mesh_subnet *sub;
 
 	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
@@ -821,6 +810,29 @@ static void gatt_proxy_set(struct bt_mesh_model *model,
 	}
 
 	cfg->gatt_proxy = buf->om_data[0];
+	if (cfg->gatt_proxy == BT_MESH_GATT_PROXY_DISABLED) {
+		int i;
+
+		/* Section 4.2.11.1: "When the GATT Proxy state is set to
+		 * 0x00, the Node Identity state for all subnets shall be set
+		 * to 0x00 and shall not be changed."
+		 */
+		for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
+			struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
+
+			if (sub->net_idx != BT_MESH_KEY_UNUSED) {
+				bt_mesh_proxy_identity_stop(sub);
+			}
+		}
+
+		/* Section 4.2.11: "Upon transition from GATT Proxy state 0x01
+		 * to GATT Proxy state 0x00 the GATT Bearer Server shall
+		 * disconnect all GATT Bearer Clients.
+		 */
+		bt_mesh_proxy_gatt_disconnect();
+	}
+
+	bt_mesh_adv_update();
 
 	sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx);
 	if ((cfg->hb_pub.feat & BT_MESH_FEAT_PROXY) && sub) {
@@ -859,14 +871,15 @@ static void net_transmit_set(struct bt_mesh_model *model,
 {
 	/* Needed size: opcode (2 bytes) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 1 + 4);
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 
 	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
 	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
 	       bt_hex(buf->om_data, buf->om_len));
 
 	BT_DBG("Transmit 0x%02x (count %u interval %ums)", buf->om_data[0],
-	       TRANSMIT_COUNT(buf->om_data[0]), TRANSMIT_INT(buf->om_data[0]));
+	       BT_MESH_TRANSMIT_COUNT(buf->om_data[0]),
+	       BT_MESH_TRANSMIT_INT(buf->om_data[0]));
 
 	if (!cfg) {
 		BT_WARN("No Configuration Server context available");
@@ -913,7 +926,7 @@ static void relay_set(struct bt_mesh_model *model,
 {
 	/* Needed size: opcode (2 bytes) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 2 + 4);
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 
 	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
 	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
@@ -922,16 +935,22 @@ static void relay_set(struct bt_mesh_model *model,
 	if (!cfg) {
 		BT_WARN("No Configuration Server context available");
 	} else if (buf->om_data[0] == 0x00 || buf->om_data[0] == 0x01) {
-		bool change = (cfg->relay != buf->om_data[0]);
 		struct bt_mesh_subnet *sub;
+		bool change;
 
-		cfg->relay = buf->om_data[0];
-		cfg->relay_retransmit = buf->om_data[1];
+		if (cfg->relay == BT_MESH_RELAY_NOT_SUPPORTED) {
+			change = false;
+		} else {
+			change = (cfg->relay != buf->om_data[0]);
+			cfg->relay = buf->om_data[0];
+			cfg->relay_retransmit = buf->om_data[1];
+		}
 
-		BT_DBG("Relay 0x%02x Retransmit 0x%02x (count %u interval %u)",
-		       cfg->relay, cfg->relay_retransmit,
-		       TRANSMIT_COUNT(cfg->relay_retransmit),
-		       TRANSMIT_INT(cfg->relay_retransmit));
+		BT_DBG("Relay 0x%02x (%s) xmit 0x%02x (count %u interval %u)",
+		       cfg->relay, change ? "changed" : "not changed",
+		       cfg->relay_retransmit,
+		       BT_MESH_TRANSMIT_COUNT(cfg->relay_retransmit),
+		       BT_MESH_TRANSMIT_INT(cfg->relay_retransmit))
 
 		sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx);
 		if ((cfg->hb_pub.feat & BT_MESH_FEAT_RELAY) && sub && change) {
@@ -989,7 +1008,9 @@ static void send_mod_pub_status(struct bt_mesh_model *cfg_mod,
 		memcpy(net_buf_simple_add(msg, 2), mod_id, 2);
 	}
 
-	bt_mesh_model_send(cfg_mod, ctx, msg, NULL, NULL);
+	if (bt_mesh_model_send(cfg_mod, ctx, msg, NULL, NULL)) {
+		BT_ERR("Unable to send Model Publication Status");
+	}
 
 	os_mbuf_free_chain(msg);
 }
@@ -1054,7 +1075,7 @@ static void mod_pub_set(struct bt_mesh_model *model,
 	pub_app_idx &= BIT_MASK(12);
 
 	pub_ttl = net_buf_simple_pull_u8(buf);
-	if (pub_ttl > 0x7f && pub_ttl != BT_MESH_TTL_DEFAULT) {
+	if (pub_ttl > BT_MESH_TTL_MAX && pub_ttl != BT_MESH_TTL_DEFAULT) {
 		BT_ERR("Invalid TTL value 0x%02x", pub_ttl);
 		return;
 	}
@@ -1068,7 +1089,8 @@ static void mod_pub_set(struct bt_mesh_model *model,
 	BT_DBG("pub_app_idx 0x%03x, pub_ttl %u pub_period 0x%02x",
 	       pub_app_idx, pub_ttl, pub_period);
 	BT_DBG("retransmit 0x%02x (count %u interval %ums)", retransmit,
-	       TRANSMIT_COUNT(retransmit), TRANSMIT_INT(retransmit));
+	       BT_MESH_PUB_TRANSMIT_COUNT(retransmit),
+	       BT_MESH_PUB_TRANSMIT_INT(retransmit));
 
 	elem = bt_mesh_elem_find(elem_addr);
 	if (!elem) {
@@ -1160,7 +1182,7 @@ static void mod_pub_va_set(struct bt_mesh_model *model,
 	cred_flag = ((pub_app_idx >> 12) & BIT_MASK(1));
 	pub_app_idx &= BIT_MASK(12);
 	pub_ttl = net_buf_simple_pull_u8(buf);
-	if (pub_ttl > 0x7f && pub_ttl != BT_MESH_TTL_DEFAULT) {
+	if (pub_ttl > BT_MESH_TTL_MAX && pub_ttl != BT_MESH_TTL_DEFAULT) {
 		BT_ERR("Invalid TTL value 0x%02x", pub_ttl);
 		return;
 	}
@@ -1174,7 +1196,8 @@ static void mod_pub_va_set(struct bt_mesh_model *model,
 	BT_DBG("pub_app_idx 0x%03x, pub_ttl %u pub_period 0x%02x",
 	       pub_app_idx, pub_ttl, pub_period);
 	BT_DBG("retransmit 0x%02x (count %u interval %ums)", retransmit,
-	       TRANSMIT_COUNT(retransmit), TRANSMIT_INT(retransmit));
+	       BT_MESH_PUB_TRANSMIT_COUNT(retransmit),
+	       BT_MESH_PUB_TRANSMIT_INT(retransmit));
 
 	elem = bt_mesh_elem_find(elem_addr);
 	if (!elem) {
@@ -1255,6 +1278,9 @@ static void send_mod_sub_status(struct bt_mesh_model *model,
 	/* Needed size: opcode (2 bytes) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 9 + 4);
 
+	BT_DBG("status 0x%02x elem_addr 0x%04x sub_addr 0x%04x", status,
+	       elem_addr, sub_addr);
+
 	bt_mesh_model_msg_init(msg, OP_MOD_SUB_STATUS);
 
 	net_buf_simple_add_u8(msg, status);
@@ -1267,7 +1293,10 @@ static void send_mod_sub_status(struct bt_mesh_model *model,
 		memcpy(net_buf_simple_add(msg, 2), mod_id, 2);
 	}
 
-	bt_mesh_model_send(model, ctx, msg, NULL, NULL);
+	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+		BT_ERR("Unable to send Model Subscription Status");
+	}
+
 	os_mbuf_free_chain(msg);
 }
 
@@ -1917,10 +1946,11 @@ static void send_net_key_status(struct bt_mesh_model *model,
 	net_buf_simple_add_u8(msg, status);
 	net_buf_simple_add_le16(msg, idx);
 
-	bt_mesh_model_send(model, ctx, msg, NULL, NULL);
+	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+		BT_ERR("Unable to send NetKey Status");
+	}
 
 	os_mbuf_free_chain(msg);
-
 }
 
 static void net_key_add(struct bt_mesh_model *model,
@@ -1979,6 +2009,9 @@ static void net_key_add(struct bt_mesh_model *model,
 
 	sub->net_idx = idx;
 
+	/* Make sure we have valid beacon data to be sent */
+	bt_mesh_net_beacon_update(sub);
+
 	if ((MYNEWT_VAL(BLE_MESH_GATT_PROXY))) {
 		sub->node_id = BT_MESH_NODE_IDENTITY_STOPPED;
 		bt_mesh_proxy_beacon_send(sub);
@@ -2029,7 +2062,7 @@ static void net_key_update(struct bt_mesh_model *model,
 			send_net_key_status(model, ctx, idx, STATUS_SUCCESS);
 			return;
 		}
-		break;
+		/* fall through */
 	case BT_MESH_KR_PHASE_2:
 	case BT_MESH_KR_PHASE_3:
 		send_net_key_status(model, ctx, idx, STATUS_CANNOT_UPDATE);
@@ -2039,7 +2072,7 @@ static void net_key_update(struct bt_mesh_model *model,
 	err = bt_mesh_net_keys_create(&sub->keys[1], buf->om_data);
 	if (!err && ((MYNEWT_VAL(BLE_MESH_LOW_POWER)) ||
 		     (MYNEWT_VAL(BLE_MESH_FRIEND)))) {
-		err = bt_mesh_friend_cred_update(ctx->net_idx, 1, buf->om_data);
+		err = friend_cred_update(sub);
 	}
 
 	if (err) {
@@ -2054,7 +2087,7 @@ static void net_key_update(struct bt_mesh_model *model,
 	send_net_key_status(model, ctx, idx, STATUS_SUCCESS);
 }
 
-static void hb_pub_disable(struct bt_mesh_cfg *cfg)
+static void hb_pub_disable(struct bt_mesh_cfg_srv *cfg)
 {
 	BT_DBG("");
 
@@ -2070,7 +2103,7 @@ static void net_key_del(struct bt_mesh_model *model,
 			struct bt_mesh_msg_ctx *ctx,
 			struct os_mbuf *buf)
 {
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 	struct bt_mesh_subnet *sub;
 	u16_t del_idx, i;
 	u8_t status;
@@ -2113,6 +2146,10 @@ static void net_key_del(struct bt_mesh_model *model,
 		}
 	}
 
+	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;
 
@@ -2240,8 +2277,17 @@ static void node_identity_set(struct bt_mesh_model *model,
 		net_buf_simple_add_u8(msg, STATUS_SUCCESS);
 		net_buf_simple_add_le16(msg, idx);
 
-		if ((MYNEWT_VAL(BLE_MESH_GATT_PROXY))) {
-			sub->node_id = node_id;
+		/* Section 4.2.11.1: "When the GATT Proxy state is set to
+		 * 0x00, the Node Identity state for all subnets shall be set
+		 * to 0x00 and shall not be changed."
+		 */
+		if (MYNEWT_VAL(BLE_MESH_GATT_PROXY) &&
+		    bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED) {
+			if (node_id) {
+				bt_mesh_proxy_identity_start(sub);
+			} else {
+				bt_mesh_proxy_identity_stop(sub);
+			}
 			bt_mesh_adv_update();
 		}
 
@@ -2429,8 +2475,11 @@ static void mod_app_get(struct bt_mesh_model *model,
 		}
 	}
 
-	bt_mesh_model_send(model, ctx, msg, NULL, NULL);
-    os_mbuf_free_chain(msg);
+	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+		BT_ERR("Unable to send Model Application List message");
+	}
+
+	os_mbuf_free_chain(msg);
 }
 
 static void node_reset(struct bt_mesh_model *model,
@@ -2439,8 +2488,6 @@ static void node_reset(struct bt_mesh_model *model,
 {
 	/* Needed size: opcode (2 bytes) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 0 + 4);
-	struct bt_mesh_cfg *cfg = model->user_data;
-	int i;
 
 	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
 	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
@@ -2456,28 +2503,6 @@ static void node_reset(struct bt_mesh_model *model,
 		BT_ERR("Unable to send Node Reset Status");
 	}
 
-	/* 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);
-		}
-	}
-
-	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
-		struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
-
-		if (cfg->hb_pub.net_idx == sub->net_idx) {
-			hb_pub_disable(cfg);
-		}
-
-		memset(sub, 0, sizeof(*sub));
-		sub->net_idx = BT_MESH_KEY_UNUSED;
-	}
-
-	memset(labels, 0, sizeof(labels));
-
 	bt_mesh_reset();
     os_mbuf_free_chain(msg);
 }
@@ -2487,7 +2512,7 @@ static void send_friend_status(struct bt_mesh_model *model,
 {
 	/* Needed size: opcode (2 bytes) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 1 + 4);
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 
 	bt_mesh_model_msg_init(msg, OP_FRIEND_STATUS);
 	net_buf_simple_add_u8(msg, cfg->frnd);
@@ -2513,7 +2538,7 @@ static void friend_set(struct bt_mesh_model *model,
 		       struct bt_mesh_msg_ctx *ctx,
 		       struct os_mbuf *buf)
 {
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 	struct bt_mesh_subnet *sub;
 
 	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
@@ -2538,6 +2563,10 @@ static void friend_set(struct bt_mesh_model *model,
 
 	if (MYNEWT_VAL(BLE_MESH_FRIEND)) {
 		cfg->frnd = buf->om_data[0];
+
+		if (cfg->frnd == BT_MESH_FRIEND_DISABLED) {
+			bt_mesh_friend_clear_net_idx(BT_MESH_KEY_ANY);
+		}
 	}
 
 	sub = bt_mesh_subnet_get(cfg->hb_pub.net_idx);
@@ -2555,7 +2584,9 @@ static void lpn_timeout_get(struct bt_mesh_model *model,
 {
 	/* Needed size: opcode (2 bytes) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 5 + 4);
+	struct bt_mesh_friend *frnd;
 	u16_t lpn_addr;
+	s32_t timeout;
 
 	lpn_addr = net_buf_simple_pull_le16(buf);
 
@@ -2570,7 +2601,24 @@ 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);
-	memset(net_buf_simple_add(msg, 3), 0, 3);
+
+	if (!IS_ENABLED(CONFIG_BLUETOOTH_MESH_FRIEND)) {
+		timeout = 0;
+		goto send_rsp;
+	}
+
+	frnd = bt_mesh_friend_find(BT_MESH_KEY_ANY, lpn_addr, true, true);
+	if (!frnd) {
+		timeout = 0;
+		goto send_rsp;
+	}
+
+	timeout = k_delayed_work_remaining_get(&frnd->timer) / 100;
+
+send_rsp:
+	net_buf_simple_add_u8(msg, timeout);
+	net_buf_simple_add_u8(msg, timeout >> 8);
+	net_buf_simple_add_u8(msg, timeout >> 16);
 
 	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
 		BT_ERR("Unable to send LPN PollTimeout Status");
@@ -2593,8 +2641,11 @@ static void send_krp_status(struct bt_mesh_model *model,
 	net_buf_simple_add_le16(msg, idx);
 	net_buf_simple_add_u8(msg, phase);
 
-	bt_mesh_model_send(model, ctx, msg, NULL, NULL);
-    os_mbuf_free_chain(msg);
+	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+		BT_ERR("Unable to send Key Refresh State Status");
+	}
+
+	os_mbuf_free_chain(msg);
 }
 
 static void krp_get(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
@@ -2663,7 +2714,7 @@ static void krp_set(struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx,
 		bt_mesh_net_revoke_keys(sub);
 		if ((MYNEWT_VAL(BLE_MESH_LOW_POWER)) ||
 		    (MYNEWT_VAL(BLE_MESH_FRIEND))) {
-			bt_mesh_friend_cred_refresh(ctx->net_idx);
+			friend_cred_refresh(ctx->net_idx);
 		}
 		sub->kr_phase = BT_MESH_KR_NORMAL;
 		sub->kr_flag = 0;
@@ -2723,7 +2774,7 @@ static void hb_pub_send_status(struct bt_mesh_model *model,
 {
 	/* Needed size: opcode (1 byte) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(1 + 10 + 4);
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 
 	BT_DBG("src 0x%04x status 0x%02x", ctx->addr, status);
 
@@ -2745,8 +2796,11 @@ static void hb_pub_send_status(struct bt_mesh_model *model,
 	net_buf_simple_add_le16(msg, cfg->hb_pub.net_idx);
 
 send:
-	bt_mesh_model_send(model, ctx, msg, NULL, NULL);
-    os_mbuf_free_chain(msg);
+	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+		BT_ERR("Unable to send Heartbeat Publication Status");
+	}
+
+	os_mbuf_free_chain(msg);
 }
 
 static void heartbeat_pub_get(struct bt_mesh_model *model,
@@ -2763,7 +2817,7 @@ static void heartbeat_pub_set(struct bt_mesh_model *model,
 			      struct os_mbuf *buf)
 {
 	struct hb_pub_param *param = (void *)buf->om_data;
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 	u16_t dst, feat, idx;
 	u8_t status;
 
@@ -2786,7 +2840,7 @@ static void heartbeat_pub_set(struct bt_mesh_model *model,
 		goto failed;
 	}
 
-	if (param->ttl > 0x7f && param->ttl != BT_MESH_TTL_DEFAULT) {
+	if (param->ttl > BT_MESH_TTL_MAX && param->ttl != BT_MESH_TTL_DEFAULT) {
 		BT_ERR("Invalid TTL value 0x%02x", param->ttl);
 		return;
 	}
@@ -2842,7 +2896,7 @@ static void hb_sub_send_status(struct bt_mesh_model *model,
 {
 	/* Needed size: opcode (2 bytes) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 9 + 4);
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 	u16_t period;
 	s64_t uptime;
 
@@ -2872,7 +2926,10 @@ static void hb_sub_send_status(struct bt_mesh_model *model,
 		net_buf_simple_add_u8(msg, cfg->hb_sub.max_hops);
 	}
 
-	bt_mesh_model_send(model, ctx, msg, NULL, NULL);
+	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+		BT_ERR("Unable to send Heartbeat Subscription Status");
+	}
+
 	os_mbuf_free_chain(msg);
 }
 
@@ -2889,7 +2946,7 @@ static void heartbeat_sub_set(struct bt_mesh_model *model,
 			      struct bt_mesh_msg_ctx *ctx,
 			      struct os_mbuf *buf)
 {
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 	u16_t sub_src, sub_dst;
 	u8_t sub_period;
 	s32_t period_ms;
@@ -2936,12 +2993,15 @@ static void heartbeat_sub_set(struct bt_mesh_model *model,
 	} else {
 		cfg->hb_sub.src = sub_src;
 		cfg->hb_sub.dst = sub_dst;
-		cfg->hb_sub.min_hops = 0x7f;
+		cfg->hb_sub.min_hops = BT_MESH_TTL_MAX;
 		cfg->hb_sub.max_hops = 0;
 		cfg->hb_sub.count = 0;
 		period_ms = hb_pwr2(sub_period, 1) * 1000;
 	}
 
+	/* Let the transport layer know it needs to handle this address */
+	bt_mesh_set_hb_sub_dst(cfg->hb_sub.dst);
+
 	BT_DBG("period_ms %u", period_ms);
 
 	if (period_ms) {
@@ -2953,7 +3013,7 @@ static void heartbeat_sub_set(struct bt_mesh_model *model,
 	hb_sub_send_status(model, ctx, STATUS_SUCCESS);
 }
 
-const struct bt_mesh_model_op bt_mesh_cfg_op[] = {
+const struct bt_mesh_model_op bt_mesh_cfg_srv_op[] = {
 	{ OP_DEV_COMP_DATA_GET,        1,   dev_comp_data_get },
 	{ OP_APP_KEY_ADD,              19,  app_key_add },
 	{ OP_APP_KEY_UPDATE,           19,  app_key_update },
@@ -3006,7 +3066,7 @@ const struct bt_mesh_model_op bt_mesh_cfg_op[] = {
 
 static void hb_publish(struct os_event *work)
 {
-	struct bt_mesh_cfg *cfg = work->ev_arg;
+	struct bt_mesh_cfg_srv *cfg = work->ev_arg;
 	struct bt_mesh_model *model = cfg->model;
 	struct bt_mesh_subnet *sub;
 	u16_t period_ms;
@@ -3037,7 +3097,7 @@ static void hb_publish(struct os_event *work)
 	}
 }
 
-static bool conf_is_valid(struct bt_mesh_cfg *cfg)
+static bool conf_is_valid(struct bt_mesh_cfg_srv *cfg)
 {
 	if (cfg->relay > 0x02) {
 		return false;
@@ -3047,16 +3107,16 @@ static bool conf_is_valid(struct bt_mesh_cfg *cfg)
 		return false;
 	}
 
-	if (cfg->default_ttl > 0x7f) {
+	if (cfg->default_ttl > BT_MESH_TTL_MAX) {
 		return false;
 	}
 
 	return true;
 }
 
-int bt_mesh_conf_init(struct bt_mesh_model *model, bool primary)
+int bt_mesh_cfg_srv_init(struct bt_mesh_model *model, bool primary)
 {
-	struct bt_mesh_cfg *cfg = model->user_data;
+	struct bt_mesh_cfg_srv *cfg = model->user_data;
 
 	if (!cfg) {
 		BT_ERR("No Configuration Server context provided");
@@ -3094,9 +3154,45 @@ int bt_mesh_conf_init(struct bt_mesh_model *model, bool primary)
 	return 0;
 }
 
+void bt_mesh_cfg_reset(void)
+{
+	struct bt_mesh_cfg_srv *cfg = conf;
+	int i;
+
+	if (!cfg) {
+		return;
+	}
+
+	bt_mesh_set_hb_sub_dst(BT_MESH_ADDR_UNASSIGNED);
+
+	cfg->hb_sub.src = BT_MESH_ADDR_UNASSIGNED;
+	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);
+		}
+	}
+
+	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;
+	}
+
+	memset(labels, 0, sizeof(labels));
+}
+
 void bt_mesh_heartbeat(u16_t src, u16_t dst, u8_t hops, u16_t feat)
 {
-	struct bt_mesh_cfg *cfg = conf;
+	struct bt_mesh_cfg_srv *cfg = conf;
 
 	if (!cfg) {
 		BT_WARN("No configuaration server context available");
@@ -3198,11 +3294,17 @@ u8_t *bt_mesh_label_uuid_get(u16_t addr)
 {
 	int i;
 
+	BT_DBG("addr 0x%04x", addr);
+
 	for (i = 0; i < ARRAY_SIZE(labels); i++) {
 		if (labels[i].addr == addr) {
+			BT_DBG("Found Label UUID for 0x%04x: %s", addr,
+			       bt_hex(labels[i].uuid, 16));
 			return labels[i].uuid;
 		}
 	}
 
+	BT_WARN("No matching Label UUID for 0x%04x", addr);
+
 	return NULL;
 }
diff --git a/net/nimble/host/mesh/src/crypto.c b/net/nimble/host/mesh/src/crypto.c
index a72ff2379..d60301897 100644
--- a/net/nimble/host/mesh/src/crypto.c
+++ b/net/nimble/host/mesh/src/crypto.c
@@ -23,6 +23,7 @@
 #include "crypto.h"
 
 #define NET_MIC_LEN(pdu) (((pdu)[1] & 0x80) ? 8 : 4)
+#define APP_MIC_LEN(aszmic) ((aszmic) ? 8 : 4)
 
 int bt_mesh_aes_cmac(const u8_t key[16], struct bt_mesh_sg *sg,
 		     size_t sg_len, u8_t mac[16])
@@ -672,15 +673,13 @@ static void create_app_nonce(u8_t nonce[13], bool dev_key, u8_t aszmic,
 
 int bt_mesh_app_encrypt(const u8_t key[16], bool dev_key, u8_t aszmic,
 			struct os_mbuf *buf, const u8_t *ad,
-			u8_t mic_len, u16_t src, u16_t dst,
-			u32_t seq_num, u32_t iv_index)
+			u16_t src, u16_t dst, u32_t seq_num, u32_t iv_index)
 {
 	u8_t nonce[13];
 	int err;
 
 	BT_DBG("AppKey %s", bt_hex(key, 16));
-	BT_DBG("dev_key %u mic_len %u src 0x%04x dst 0x%04x", dev_key,
-	       mic_len, src, dst);
+	BT_DBG("dev_key %u src 0x%04x dst 0x%04x", dev_key, src, dst);
 	BT_DBG("seq_num 0x%08x iv_index 0x%08x", seq_num, iv_index);
 	BT_DBG("Clear: %s", bt_hex(buf->om_data, buf->om_len));
 
@@ -689,9 +688,9 @@ int bt_mesh_app_encrypt(const u8_t key[16], bool dev_key, u8_t aszmic,
 	BT_DBG("Nonce  %s", bt_hex(nonce, 13));
 
 	err = bt_mesh_ccm_encrypt(key, nonce, buf->om_data, buf->om_len, ad,
-				  ad ? 16 : 0, buf->om_data, mic_len);
+				  ad ? 16 : 0, buf->om_data, APP_MIC_LEN(aszmic));
 	if (!err) {
-		net_buf_simple_add(buf, mic_len);
+		net_buf_simple_add(buf, APP_MIC_LEN(aszmic));
 		BT_DBG("Encr: %s", bt_hex(buf->om_data, buf->om_len));
 	}
 
@@ -699,9 +698,8 @@ int bt_mesh_app_encrypt(const u8_t key[16], bool dev_key, u8_t aszmic,
 }
 
 int bt_mesh_app_decrypt(const u8_t key[16], bool dev_key, u8_t aszmic,
-			struct os_mbuf *buf, u8_t mic_len,
-			struct os_mbuf *out, const u8_t *ad,
-			u16_t src, u16_t dst, u32_t seq_num,
+			struct os_mbuf *buf, struct os_mbuf *out,
+			const u8_t *ad, u16_t src, u16_t dst, u32_t seq_num,
 			u32_t iv_index)
 {
 	u8_t nonce[13];
@@ -715,7 +713,7 @@ int bt_mesh_app_decrypt(const u8_t key[16], bool dev_key, u8_t aszmic,
 	BT_DBG("Nonce  %s", bt_hex(nonce, 13));
 
 	err = bt_mesh_ccm_decrypt(key, nonce, buf->om_data, buf->om_len, ad,
-				  ad ? 16 : 0, out->om_data, mic_len);
+				  ad ? 16 : 0, out->om_data, APP_MIC_LEN(aszmic));
 	if (!err) {
 		net_buf_simple_add(out, buf->om_len);
 	}
diff --git a/net/nimble/host/mesh/src/crypto.h b/net/nimble/host/mesh/src/crypto.h
index d37b7edab..a56e6b9e8 100644
--- a/net/nimble/host/mesh/src/crypto.h
+++ b/net/nimble/host/mesh/src/crypto.h
@@ -86,8 +86,8 @@ static inline int bt_mesh_session_key(const u8_t dhkey[32],
 }
 
 static inline int bt_mesh_prov_nonce(const u8_t dhkey[32],
-				      const u8_t prov_salt[16],
-				      u8_t nonce[13])
+				     const u8_t prov_salt[16],
+				     u8_t nonce[13])
 {
 	u8_t tmp[16];
 	int err;
@@ -132,14 +132,12 @@ int bt_mesh_net_decrypt(const u8_t key[16], struct os_mbuf *buf,
 			u32_t iv_index, bool proxy);
 
 int bt_mesh_app_encrypt(const u8_t key[16], bool dev_key, u8_t aszmic,
-			struct os_mbuf *buf, const u8_t *ad,
-			u8_t mic_len, u16_t src, u16_t dst,
-			u32_t seq_num, u32_t iv_index);
+			struct os_mbuf*buf, const u8_t *ad,
+			u16_t src, u16_t dst, u32_t seq_num, u32_t iv_index);
 
 int bt_mesh_app_decrypt(const u8_t key[16], bool dev_key, u8_t aszmic,
-			struct os_mbuf *buf, u8_t mic_len,
-			struct os_mbuf *out, const u8_t *ad,
-			u16_t src, u16_t dst, u32_t seq_num,
+			struct os_mbuf*buf, struct os_mbuf*out,
+			const u8_t *ad, u16_t src, u16_t dst, u32_t seq_num,
 			u32_t iv_index);
 
 u8_t bt_mesh_fcs_calc(const u8_t *data, u8_t data_len);
diff --git a/net/nimble/host/mesh/src/foundation.h b/net/nimble/host/mesh/src/foundation.h
index ec48f1e4e..14e4c72a4 100644
--- a/net/nimble/host/mesh/src/foundation.h
+++ b/net/nimble/host/mesh/src/foundation.h
@@ -115,38 +115,41 @@
 #define STATUS_UNSPECIFIED                 0x10
 #define STATUS_INVALID_BINDING             0x11
 
-int
-bt_mesh_conf_init(struct bt_mesh_model *model, bool primary);
-int
-bt_mesh_health_init(struct bt_mesh_model *model, bool primary);
+int bt_mesh_cfg_srv_init(struct bt_mesh_model *model, bool primary);
+int bt_mesh_health_srv_init(struct bt_mesh_model *model, bool primary);
 
-void
-bt_mesh_heartbeat(u16_t src, u16_t dst, u8_t hops, u16_t feat);
+int bt_mesh_cfg_cli_init(struct bt_mesh_model *model, bool primary);
+int bt_mesh_health_cli_init(struct bt_mesh_model *model, bool primary);
 
-void
-bt_mesh_attention(struct bt_mesh_model *model, u8_t time);
+void bt_mesh_cfg_reset(void);
 
-u8_t *
-bt_mesh_label_uuid_get(u16_t addr);
+void bt_mesh_heartbeat(u16_t src, u16_t dst, u8_t hops, u16_t feat);
 
-/* Transmission count (N + 1) */
-#define TRANSMIT_COUNT(transmit) (((transmit) & (u8_t)BIT_MASK(3)))
-/* Returns transmission interval in milliseconds */
-#define TRANSMIT_INT(transmit) ((((transmit) >> 3) + 1) * 10)
+void bt_mesh_attention(struct bt_mesh_model *model, u8_t time);
 
-u8_t
-bt_mesh_net_transmit_get(void);
-u8_t
-bt_mesh_relay_get(void);
-u8_t
-bt_mesh_friend_get(void);
-u8_t
-bt_mesh_relay_retransmit_get(void);
-u8_t
-bt_mesh_beacon_get(void);
-u8_t
-bt_mesh_gatt_proxy_get(void);
-u8_t
-bt_mesh_default_ttl_get(void);
+u8_t *bt_mesh_label_uuid_get(u16_t addr);
+
+u8_t bt_mesh_net_transmit_get(void);
+u8_t bt_mesh_relay_get(void);
+u8_t bt_mesh_friend_get(void);
+u8_t bt_mesh_relay_retransmit_get(void);
+u8_t bt_mesh_beacon_get(void);
+u8_t bt_mesh_gatt_proxy_get(void);
+u8_t bt_mesh_default_ttl_get(void);
+
+static inline void key_idx_pack(struct os_mbuf *buf,
+				u16_t idx1, u16_t idx2)
+{
+	net_buf_simple_add_le16(buf, idx1 | ((idx2 & 0x00f) << 12));
+	net_buf_simple_add_u8(buf, idx2 >> 4);
+}
+
+static inline void key_idx_unpack(struct os_mbuf *buf,
+				  u16_t *idx1, u16_t *idx2)
+{
+	*idx1 = sys_get_le16(&buf->om_data[0]) & 0xfff;
+	*idx2 = sys_get_le16(&buf->om_data[1]) >> 4;
+	net_buf_simple_pull(buf, 3);
+}
 
 #endif
diff --git a/net/nimble/host/mesh/src/friend.c b/net/nimble/host/mesh/src/friend.c
index 7baa43594..053f35ebb 100644
--- a/net/nimble/host/mesh/src/friend.c
+++ b/net/nimble/host/mesh/src/friend.c
@@ -18,198 +18,1312 @@
 #include "host/ble_hs_log.h"
 
 #include "mesh/mesh.h"
+#include "mesh/slist.h"
 #include "mesh_priv.h"
 #include "crypto.h"
 #include "adv.h"
 #include "net.h"
 #include "transport.h"
 #include "access.h"
+#include "foundation.h"
 #include "friend.h"
 
-static int send_friend_update(void)
+#define FRIEND_BUF_SIZE     (BT_MESH_ADV_DATA_SIZE - BT_MESH_NET_HDR_LEN)
+
+/* We reserve one extra buffer for each friendship, since we need to be able
+ * to resend the last sent PDU, which sits separately outside of the queue.
+ */
+#define FRIEND_BUF_COUNT ((MYNEWT_VAL(BLE_MESH_FRIEND_QUEUE_SIZE) + 1) * MYNEWT_VAL(BLE_MESH_FRIEND_LPN_COUNT))
+
+static os_membuf_t friend_buf_mem[OS_MEMPOOL_SIZE(
+		FRIEND_BUF_COUNT,
+		BT_MESH_ADV_DATA_SIZE + BT_MESH_ADV_USER_DATA_SIZE)];
+
+struct os_mbuf_pool friend_os_mbuf_pool;
+static struct os_mempool friend_buf_mempool;
+
+
+#define FRIEND_ADV(buf)     CONTAINER_OF(BT_MESH_ADV(buf), \
+					 struct friend_adv, adv)
+
+struct friend_pdu_info {
+	u16_t  src;
+	u16_t  dst;
+
+	u8_t   seq[3];
+
+	u8_t   ttl:7,
+	       ctl:1;
+
+	u32_t  iv_index;
+};
+
+static struct friend_adv {
+	struct bt_mesh_adv adv;
+	u64_t seq_auth;
+} adv_pool[FRIEND_BUF_COUNT];
+
+static struct bt_mesh_adv *adv_alloc(int id)
 {
-	struct bt_mesh_msg_ctx ctx = {
-		.net_idx     = bt_mesh.sub[0].net_idx,
-		.app_idx     = BT_MESH_KEY_UNUSED,
-		.addr        = bt_mesh.frnd.lpn,
-		.send_ttl    = 0,
-		.friend_cred = 1,
-	};
+	return &adv_pool[id].adv;
+}
+
+static void discard_buffer(void)
+{
+	struct bt_mesh_friend *frnd = &bt_mesh.frnd[0];
+	struct os_mbuf *buf;
+	int i;
+
+	/* Find the Friend context with the most queued buffers */
+	for (i = 1; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
+		if (bt_mesh.frnd[i].queue_size > frnd->queue_size) {
+			frnd = &bt_mesh.frnd[i];
+		}
+	}
+
+	buf = net_buf_slist_get(&frnd->queue);
+	__ASSERT_NO_MSG(buf != NULL);
+	BT_WARN("Discarding buffer %p for LPN 0x%04x", buf, frnd->lpn);
+	net_buf_unref(buf);
+}
+
+static struct os_mbuf *friend_buf_alloc(u16_t src)
+{
+	u8_t xmit = bt_mesh_net_transmit_get();
+	struct os_mbuf *buf;
+
+	do {
+		buf = bt_mesh_adv_create_from_pool(&friend_os_mbuf_pool, adv_alloc,
+						   BT_MESH_ADV_DATA,
+						   BT_MESH_TRANSMIT_COUNT(xmit),
+						   BT_MESH_TRANSMIT_INT(xmit),
+						   K_NO_WAIT);
+		if (!buf) {
+			discard_buffer();
+		}
+	} while (!buf);
+
+	BT_MESH_ADV(buf)->addr = src;
+	FRIEND_ADV(buf)->seq_auth = TRANS_SEQ_AUTH_NVAL;
+
+	BT_DBG("allocated buf %p", buf);
+
+	return buf;
+}
+
+struct bt_mesh_friend *bt_mesh_friend_find(u16_t net_idx, u16_t lpn_addr,
+					   bool valid, bool established)
+{
+	int i;
+
+	BT_DBG("net_idx 0x%04x lpn_addr 0x%04x", net_idx, lpn_addr);
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
+		struct bt_mesh_friend *frnd = &bt_mesh.frnd[i];
+
+		if (valid && !frnd->valid) {
+			continue;
+		}
+
+		if (established && !frnd->established) {
+			continue;
+		}
+
+		if (net_idx != BT_MESH_KEY_ANY && frnd->net_idx != net_idx) {
+			continue;
+		}
+
+		if (frnd->lpn == lpn_addr) {
+			return frnd;
+		}
+	}
+
+	return NULL;
+}
+
+/* Intentionally start a little bit late into the ReceiveWindow when
+ * it's large enough. This may improve reliability with some platforms,
+ * like the PTS, where the receiver might not have sufficiently compensated
+ * for internal latencies required to start scanning.
+ */
+static s32_t recv_delay(struct bt_mesh_friend *frnd)
+{
+#if CONFIG_BT_MESH_FRIEND_RECV_WIN > 50
+	return (s32_t)frnd->recv_delay + (CONFIG_BT_MESH_FRIEND_RECV_WIN / 5);
+#else
+	return frnd->recv_delay;
+#endif
+}
+
+static void friend_clear(struct bt_mesh_friend *frnd)
+{
+	int i;
+
+	BT_DBG("LPN 0x%04x", frnd->lpn);
+
+	k_delayed_work_cancel(&frnd->timer);
+
+	friend_cred_del(frnd->net_idx, frnd->lpn);
+
+	if (frnd->last) {
+		/* Cancel the sending if necessary */
+		if (frnd->pending_buf) {
+			BT_MESH_ADV(frnd->last)->busy = 0;
+		}
+
+		net_buf_unref(frnd->last);
+		frnd->last = NULL;
+	}
+
+	while (!sys_slist_is_empty(&frnd->queue)) {
+		net_buf_unref(net_buf_slist_get(&frnd->queue));
+	}
+
+	for (i = 0; i < ARRAY_SIZE(frnd->seg); i++) {
+		struct bt_mesh_friend_seg *seg = &frnd->seg[i];
+
+		while (!sys_slist_is_empty(&seg->queue)) {
+			net_buf_unref(net_buf_slist_get(&seg->queue));
+		}
+	}
+
+	frnd->valid = 0;
+	frnd->established = 0;
+	frnd->pending_buf = 0;
+	frnd->fsn = 0;
+	frnd->queue_size = 0;
+	frnd->pending_req = 0;
+	memset(frnd->sub_list, 0, sizeof(frnd->sub_list));
+}
+
+void bt_mesh_friend_clear_net_idx(u16_t net_idx)
+{
+	int i;
+
+	BT_DBG("net_idx 0x%04x", net_idx);
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
+		struct bt_mesh_friend *frnd = &bt_mesh.frnd[i];
+
+		if (frnd->net_idx == BT_MESH_KEY_UNUSED) {
+			continue;
+		}
+
+		if (net_idx == BT_MESH_KEY_ANY || frnd->net_idx == net_idx) {
+			friend_clear(frnd);
+		}
+	}
+}
+
+void bt_mesh_friend_sec_update(u16_t net_idx)
+{
+	int i;
+
+	BT_DBG("net_idx 0x%04x", net_idx);
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
+		struct bt_mesh_friend *frnd = &bt_mesh.frnd[i];
+
+		if (frnd->net_idx == BT_MESH_KEY_UNUSED) {
+			continue;
+		}
+
+		if (net_idx == BT_MESH_KEY_ANY || frnd->net_idx == net_idx) {
+			frnd->sec_update = 1;
+		}
+	}
+}
+
+int bt_mesh_friend_clear(struct bt_mesh_net_rx *rx, struct os_mbuf *buf)
+{
+	struct bt_mesh_ctl_friend_clear *msg = (void *)buf->om_data;
+	struct bt_mesh_friend *frnd;
+	u16_t lpn_addr, lpn_counter;
 	struct bt_mesh_net_tx tx = {
-		.sub = &bt_mesh.sub[0],
-		.ctx = &ctx,
-		.src = bt_mesh_primary_addr(),
-	};
-	struct bt_mesh_ctl_friend_update upd = {
-		.flags = 0,
-		.iv_index = sys_cpu_to_be32(bt_mesh.iv_index),
-		.md = !k_fifo_is_empty(&bt_mesh.frnd.queue),
+		.sub  = rx->sub,
+		.ctx  = &rx->ctx,
+		.src  = bt_mesh_primary_addr(),
+		.xmit = bt_mesh_net_transmit_get(),
 	};
+	struct bt_mesh_ctl_friend_clear_confirm cfm;
 
-	BT_DBG("");
+	if (buf->om_len < sizeof(*msg)) {
+		BT_WARN("Too short Friend Clear");
+		return -EINVAL;
+	}
+
+	lpn_addr = sys_be16_to_cpu(msg->lpn_addr);
+	lpn_counter = sys_be16_to_cpu(msg->lpn_counter);
+
+	BT_DBG("LPN addr 0x%04x counter 0x%04x", lpn_addr, lpn_counter);
+
+	frnd = bt_mesh_friend_find(rx->sub->net_idx, lpn_addr, false, false);
+	if (!frnd) {
+		BT_WARN("No matching LPN addr 0x%04x", lpn_addr);
+		return 0;
+	}
+
+	/* A Friend Clear message is considered valid if the result of the
+	 * subtraction of the value of the LPNCounter field of the Friend
+	 * Request message (the one that initiated the friendship) from the
+	 * value of the LPNCounter field of the Friend Clear message, modulo
+	 * 65536, is in the range 0 to 255 inclusive.
+	 */
+	if (lpn_counter - frnd->lpn_counter > 255) {
+		BT_WARN("LPN Counter out of range (old %u new %u)",
+			frnd->lpn_counter, lpn_counter);
+		return 0;
+	}
 
-	return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_UPDATE, &upd,
-				sizeof(upd), NULL);
+	tx.ctx->send_ttl = BT_MESH_TTL_MAX;
+
+	cfm.lpn_addr    = msg->lpn_addr;
+	cfm.lpn_counter = msg->lpn_counter;
+
+	bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR_CFM, &cfm,
+			 sizeof(cfm), NULL, NULL, NULL);
+
+	friend_clear(frnd);
+
+	return 0;
+}
+
+static void friend_sub_add(struct bt_mesh_friend *frnd, u16_t addr)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(frnd->sub_list); i++) {
+		if (frnd->sub_list[i] == BT_MESH_ADDR_UNASSIGNED) {
+			frnd->sub_list[i] = addr;
+			return;
+		}
+	}
+
+	BT_WARN("No space in friend subscription list");
+}
+
+static void friend_sub_rem(struct bt_mesh_friend *frnd, u16_t addr)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(frnd->sub_list); i++) {
+		if (frnd->sub_list[i] == addr) {
+			frnd->sub_list[i] = BT_MESH_ADDR_UNASSIGNED;
+			return;
+		}
+	}
+}
+
+static struct os_mbuf *create_friend_pdu(struct bt_mesh_friend *frnd,
+					 struct friend_pdu_info *info,
+					 struct os_mbuf *sdu)
+{
+	struct bt_mesh_subnet *sub;
+	const u8_t *enc, *priv;
+	struct os_mbuf *buf;
+	u8_t nid;
+
+	sub = bt_mesh_subnet_get(frnd->net_idx);
+	__ASSERT_NO_MSG(sub != NULL);
+
+	buf = friend_buf_alloc(info->src);
+
+	/* Friend Offer needs master security credentials */
+	if (info->ctl && TRANS_CTL_OP(sdu->om_data) == TRANS_CTL_OP_FRIEND_OFFER) {
+		enc = sub->keys[sub->kr_flag].enc;
+		priv = sub->keys[sub->kr_flag].privacy;
+		nid = sub->keys[sub->kr_flag].nid;
+	} else {
+		if (friend_cred_get(sub, frnd->lpn, &nid, &enc, &priv)) {
+			BT_ERR("friend_cred_get failed");
+			goto failed;
+		}
+	}
+
+	net_buf_add_u8(buf, (nid | (info->iv_index & 1) << 7));
+
+	if (info->ctl) {
+		net_buf_add_u8(buf, info->ttl | 0x80);
+	} else {
+		net_buf_add_u8(buf, info->ttl);
+	}
+
+	net_buf_add_mem(buf, info->seq, sizeof(info->seq));
+
+	net_buf_add_be16(buf, info->src);
+	net_buf_add_be16(buf, info->dst);
+
+	net_buf_add_mem(buf, sdu->om_data, sdu->om_len);
+
+	/* We re-encrypt and obfuscate using the received IVI rather than
+	 * the normal TX IVI (which may be different) since the transport
+	 * layer nonce includes the IVI.
+	 */
+	if (bt_mesh_net_encrypt(enc, buf, info->iv_index, false)) {
+		BT_ERR("Re-encrypting failed");
+		goto failed;
+	}
+
+	if (bt_mesh_net_obfuscate(buf->om_data, info->iv_index, priv)) {
+		BT_ERR("Re-obfuscating failed");
+		goto failed;
+	}
+
+	return buf;
+
+failed:
+	net_buf_unref(buf);
+	return NULL;
+}
+
+static struct os_mbuf *encode_friend_ctl(struct bt_mesh_friend *frnd,
+					 u8_t ctl_op,
+					 struct os_mbuf *sdu)
+{
+	struct friend_pdu_info info;
+
+	BT_DBG("LPN 0x%04x", frnd->lpn);
+
+	net_buf_simple_push_u8(sdu, TRANS_CTL_HDR(ctl_op, 0));
+
+	info.src = bt_mesh_primary_addr();
+	info.dst = frnd->lpn;
+
+	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++;
+
+	info.iv_index = BT_MESH_NET_IVI_TX;
+
+	return create_friend_pdu(frnd, &info, sdu);
+}
+
+static struct os_mbuf *encode_update(struct bt_mesh_friend *frnd, u8_t md)
+{
+	struct bt_mesh_ctl_friend_update *upd;
+	struct os_mbuf *sdu = NET_BUF_SIMPLE(1 + sizeof(*upd));
+	struct bt_mesh_subnet *sub = bt_mesh_subnet_get(frnd->net_idx);
+
+	__ASSERT_NO_MSG(sub != NULL);
+
+	BT_DBG("lpn 0x%04x md 0x%02x", frnd->lpn, md);
+
+	net_buf_simple_init(sdu, 1);
+
+	upd = net_buf_simple_add(sdu, sizeof(*upd));
+	upd->flags = bt_mesh_net_flags(sub);
+	upd->iv_index = sys_cpu_to_be32(bt_mesh.iv_index);
+	upd->md = md;
+
+	return encode_friend_ctl(frnd, TRANS_CTL_OP_FRIEND_UPDATE, sdu);
+}
+
+static void enqueue_sub_cfm(struct bt_mesh_friend *frnd, u8_t xact)
+{
+	struct bt_mesh_ctl_friend_sub_confirm *cfm;
+	struct os_mbuf *sdu = NET_BUF_SIMPLE(1 + sizeof(*cfm));
+	struct os_mbuf *buf;
+
+	BT_DBG("lpn 0x%04x xact 0x%02x", frnd->lpn, xact);
+
+	net_buf_simple_init(sdu, 1);
+
+	cfm = net_buf_simple_add(sdu, sizeof(*cfm));
+	cfm->xact = xact;
+
+	buf = encode_friend_ctl(frnd, TRANS_CTL_OP_FRIEND_SUB_CFM, sdu);
+	if (!buf) {
+		BT_ERR("Unable to encode Subscription List Confirmation");
+		return;
+	}
+
+	if (frnd->last) {
+		BT_DBG("Discarding last PDU");
+		net_buf_unref(frnd->last);
+	}
+
+	frnd->last = buf;
+	frnd->send_last = 1;
+}
+
+static void friend_recv_delay(struct bt_mesh_friend *frnd)
+{
+	frnd->pending_req = 1;
+	k_delayed_work_submit(&frnd->timer, recv_delay(frnd));
+	BT_DBG("Waiting RecvDelay of %d ms", recv_delay(frnd));
+}
+
+int bt_mesh_friend_sub_add(struct bt_mesh_net_rx *rx,
+			   struct os_mbuf *buf)
+{
+	struct bt_mesh_friend *frnd;
+	u8_t xact;
+
+	if (buf->om_len < BT_MESH_FRIEND_SUB_MIN_LEN) {
+		BT_WARN("Too short Friend Subscription Add");
+		return -EINVAL;
+	}
+
+	frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, true);
+	if (!frnd) {
+		BT_WARN("No matching LPN addr 0x%04x", rx->ctx.addr);
+		return 0;
+	}
+
+	if (frnd->pending_buf) {
+		BT_WARN("Previous buffer not yet sent!");
+		return 0;
+	}
+
+	friend_recv_delay(frnd);
+
+	xact = net_buf_simple_pull_u8(buf);
+
+	while (buf->om_len >= 2) {
+		friend_sub_add(frnd, net_buf_simple_pull_be16(buf));
+	}
+
+	enqueue_sub_cfm(frnd, xact);
+
+	return 0;
+}
+
+int bt_mesh_friend_sub_rem(struct bt_mesh_net_rx *rx,
+			   struct os_mbuf *buf)
+{
+	struct bt_mesh_friend *frnd;
+	u8_t xact;
+
+	if (buf->om_len < BT_MESH_FRIEND_SUB_MIN_LEN) {
+		BT_WARN("Too short Friend Subscription Remove");
+		return -EINVAL;
+	}
+
+	frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, true);
+	if (!frnd) {
+		BT_WARN("No matching LPN addr 0x%04x", rx->ctx.addr);
+		return 0;
+	}
+
+	if (frnd->pending_buf) {
+		BT_WARN("Previous buffer not yet sent!");
+		return 0;
+	}
+
+	friend_recv_delay(frnd);
+
+	xact = net_buf_simple_pull_u8(buf);
+
+	while (buf->om_len >= 2) {
+		friend_sub_rem(frnd, net_buf_simple_pull_be16(buf));
+	}
+
+	enqueue_sub_cfm(frnd, xact);
+
+	return 0;
+}
+
+static void enqueue_buf(struct bt_mesh_friend *frnd, struct os_mbuf *buf)
+{
+	net_buf_slist_put(&frnd->queue, buf);
+	frnd->queue_size++;
+}
+
+static void enqueue_update(struct bt_mesh_friend *frnd, u8_t md)
+{
+	struct os_mbuf *buf;
+
+	buf = encode_update(frnd, md);
+	if (!buf) {
+		BT_ERR("Unable to encode Friend Update");
+		return;
+	}
+
+	frnd->sec_update = 0;
+	enqueue_buf(frnd, buf);
 }
 
 int bt_mesh_friend_poll(struct bt_mesh_net_rx *rx, struct os_mbuf *buf)
 {
 	struct bt_mesh_ctl_friend_poll *msg = (void *)buf->om_data;
-	struct bt_mesh_friend *frnd = &bt_mesh.frnd;
+	struct bt_mesh_friend *frnd;
 
 	if (buf->om_len < sizeof(*msg)) {
-		BT_WARN("Too short Friend Update");
+		BT_WARN("Too short Friend Poll");
 		return -EINVAL;
 	}
 
-	BT_DBG("msg->fsn 0x%02x frnd->fsn 0x%02x", msg->fsn, frnd->fsn);
+	frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, false);
+	if (!frnd) {
+		BT_WARN("No matching LPN addr 0x%04x", rx->ctx.addr);
+		return 0;
+	}
+
+	if (msg->fsn & ~1) {
+		BT_WARN("Prohibited (non-zero) padding bits");
+		return -EINVAL;
+	}
 
-	if (msg->fsn != frnd->fsn) {
-		frnd->send_last = 1;
+	if (frnd->pending_buf) {
+		BT_WARN("Previous buffer not yet sent");
+		return 0;
 	}
 
-	frnd->fsn++;
-	frnd->send_update = 1;
+	BT_DBG("msg->fsn %u frnd->fsn %u", (msg->fsn & 1), frnd->fsn);
+
+	friend_recv_delay(frnd);
+
+	if (!frnd->established) {
+		BT_DBG("Friendship established with 0x%04x", frnd->lpn);
+		frnd->established = 1;
+	}
+
+	if (msg->fsn == frnd->fsn && frnd->last) {
+		BT_DBG("Re-sending last PDU");
+		frnd->send_last = 1;
+	} else {
+		if (frnd->last) {
+			net_buf_unref(frnd->last);
+			frnd->last = NULL;
+		}
+
+		frnd->fsn = msg->fsn;
 
-	k_delayed_work_submit(&frnd->timer, frnd->recv_delay);
+		if (sys_slist_is_empty(&frnd->queue)) {
+			enqueue_update(frnd, 0);
+			BT_DBG("Enqueued Friend Update to empty queue");
+		}
+	}
 
 	return 0;
 }
 
-static int send_friend_offer(s8_t rssi)
+static struct bt_mesh_friend *find_clear(u16_t prev_friend)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
+		struct bt_mesh_friend *frnd = &bt_mesh.frnd[i];
+
+		if (frnd->clear.frnd == prev_friend) {
+			return frnd;
+		}
+	}
+
+	return NULL;
+}
+
+static void friend_clear_sent(int err, void *user_data)
+{
+	struct bt_mesh_friend *frnd = user_data;
+
+	k_delayed_work_submit(&frnd->clear.timer,
+			      K_SECONDS(frnd->clear.repeat_sec));
+	frnd->clear.repeat_sec *= 2;
+}
+
+static const struct bt_mesh_send_cb clear_sent_cb = {
+	.end = friend_clear_sent,
+};
+
+static void send_friend_clear(struct bt_mesh_friend *frnd)
 {
 	struct bt_mesh_msg_ctx ctx = {
-		.net_idx     = bt_mesh.sub[0].net_idx,
-		.app_idx     = BT_MESH_KEY_UNUSED,
-		.addr        = bt_mesh.frnd.lpn,
-		.send_ttl    = 0,
+		.net_idx  = frnd->net_idx,
+		.app_idx  = BT_MESH_KEY_UNUSED,
+		.addr     = frnd->clear.frnd,
+		.send_ttl = BT_MESH_TTL_MAX,
 	};
 	struct bt_mesh_net_tx tx = {
-		.sub = &bt_mesh.sub[0],
-		.ctx = &ctx,
-		.src = bt_mesh_primary_addr(),
+		.sub  = &bt_mesh.sub[0],
+		.ctx  = &ctx,
+		.src  = bt_mesh_primary_addr(),
+		.xmit = bt_mesh_net_transmit_get(),
 	};
-	struct bt_mesh_ctl_friend_offer off = {
-		.recv_win = MYNEWT_VAL(BLE_MESH_FRIEND_RECV_WIN),
-		.queue_size = MYNEWT_VAL(BLE_MESH_FRIEND_QUEUE_SIZE),
-		.rssi = rssi,
-		.frnd_counter = bt_mesh.frnd.counter++,
+	struct bt_mesh_ctl_friend_clear req = {
+		.lpn_addr    = sys_cpu_to_be16(frnd->lpn),
+		.lpn_counter = sys_cpu_to_be16(frnd->lpn_counter),
 	};
 
 	BT_DBG("");
 
-	return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_OFFER, &off,
-				sizeof(off), NULL);
+	bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR, &req,
+			 sizeof(req), NULL, &clear_sent_cb, frnd);
+}
+
+static void clear_timeout(struct os_event *work)
+{
+	struct bt_mesh_friend *frnd = work->ev_arg;
+	u32_t duration;
+
+	BT_DBG("LPN 0x%04x (old) Friend 0x%04x", frnd->lpn, frnd->clear.frnd);
+
+	duration = k_uptime_get_32() - frnd->clear.start;
+	if (duration > 2 * frnd->poll_to) {
+		BT_DBG("Clear Procedure timer expired");
+		frnd->clear.frnd = BT_MESH_ADDR_UNASSIGNED;
+		return;
+	}
+
+	send_friend_clear(frnd);
+}
+
+static void clear_procedure_start(struct bt_mesh_friend *frnd)
+{
+	BT_DBG("LPN 0x%04x (old) Friend 0x%04x", frnd->lpn, frnd->clear.frnd);
+
+	frnd->clear.start = k_uptime_get_32() + (2 * frnd->poll_to);
+	frnd->clear.repeat_sec = 1;
+
+	send_friend_clear(frnd);
+}
+
+int bt_mesh_friend_clear_cfm(struct bt_mesh_net_rx *rx,
+			     struct os_mbuf *buf)
+{
+	struct bt_mesh_ctl_friend_clear_confirm *msg = (void *)buf->om_data;
+	struct bt_mesh_friend *frnd;
+	u16_t lpn_addr, lpn_counter;
+
+	BT_DBG("");
+
+	if (buf->om_len < sizeof(*msg)) {
+		BT_WARN("Too short Friend Clear Confirm");
+		return -EINVAL;
+	}
+
+	frnd = find_clear(rx->ctx.addr);
+	if (!frnd) {
+		BT_WARN("No pending clear procedure for 0x%02x", rx->ctx.addr);
+		return 0;
+	}
+
+	lpn_addr = sys_be16_to_cpu(msg->lpn_addr);
+	if (lpn_addr != frnd->lpn) {
+		BT_WARN("LPN address mismatch (0x%04x != 0x%04x)",
+			lpn_addr, frnd->lpn);
+		return 0;
+	}
+
+	lpn_counter = sys_be16_to_cpu(msg->lpn_counter);
+	if (lpn_counter != frnd->lpn_counter) {
+		BT_WARN("LPN counter mismatch (0x%04x != 0x%04x)",
+			lpn_counter, frnd->lpn_counter);
+		return 0;
+	}
+
+	k_delayed_work_cancel(&frnd->clear.timer);
+	frnd->clear.frnd = BT_MESH_ADDR_UNASSIGNED;
+
+	return 0;
+}
+
+static void enqueue_offer(struct bt_mesh_friend *frnd, s8_t rssi)
+{
+	struct bt_mesh_ctl_friend_offer *off;
+	struct os_mbuf *sdu = NET_BUF_SIMPLE(1 + sizeof(*off));
+	struct os_mbuf *buf;
+
+	BT_DBG("");
+
+	net_buf_simple_init(sdu, 1);
+
+	off = net_buf_simple_add(sdu, sizeof(*off));
+
+	off->recv_win = CONFIG_BT_MESH_FRIEND_RECV_WIN,
+	off->queue_size = CONFIG_BT_MESH_FRIEND_QUEUE_SIZE,
+	off->sub_list_size = ARRAY_SIZE(frnd->sub_list),
+	off->rssi = rssi,
+	off->frnd_counter = sys_cpu_to_be16(frnd->counter);
+
+	buf = encode_friend_ctl(frnd, TRANS_CTL_OP_FRIEND_OFFER, sdu);
+	if (!buf) {
+		BT_ERR("Unable to encode Friend Offer");
+		return;
+	}
+
+	frnd->counter++;
+
+	if (frnd->last) {
+		net_buf_unref(frnd->last);
+	}
+
+	frnd->last = buf;
+	frnd->send_last = 1;
+}
+
+#define RECV_WIN                  CONFIG_BT_MESH_FRIEND_RECV_WIN
+#define RSSI_FACT(crit)           (((crit) >> 5) & (u8_t)BIT_MASK(2))
+#define RECV_WIN_FACT(crit)       (((crit) >> 3) & (u8_t)BIT_MASK(2))
+#define MIN_QUEUE_SIZE_LOG(crit)  ((crit) & (u8_t)BIT_MASK(3))
+#define MIN_QUEUE_SIZE(crit)      ((u32_t)BIT(MIN_QUEUE_SIZE_LOG(crit)))
+
+static s32_t offer_delay(struct bt_mesh_friend *frnd, s8_t rssi, u8_t crit)
+{
+	/* Scaling factors. The actual values are 1, 1.5, 2 & 2.5, but we
+	 * want to avoid floating-point arithmetic.
+	 */
+	static const u8_t fact[] = { 10, 15, 20, 25 };
+	s32_t delay;
+
+	BT_DBG("ReceiveWindowFactor %u ReceiveWindow %u RSSIFactor %u RSSI %d",
+	       fact[RECV_WIN_FACT(crit)], RECV_WIN,
+	       fact[RSSI_FACT(crit)], rssi);
+
+	/* Delay = ReceiveWindowFactor * ReceiveWindow - RSSIFactor * RSSI */
+	delay = (s32_t)fact[RECV_WIN_FACT(crit)] * RECV_WIN;
+	delay -= (s32_t)fact[RSSI_FACT(crit)] * rssi;
+	delay /= 10;
+
+	BT_DBG("Local Delay calculated as %d ms", delay);
+
+	if (delay < 100) {
+		return K_MSEC(100);
+	}
+
+	return K_MSEC(delay);
 }
 
 int bt_mesh_friend_req(struct bt_mesh_net_rx *rx, struct os_mbuf *buf)
 {
 	struct bt_mesh_ctl_friend_req *msg = (void *)buf->om_data;
-	struct bt_mesh_friend *frnd = &bt_mesh.frnd;
-	struct bt_mesh_subnet *sub = rx->sub;
+	struct bt_mesh_friend *frnd = NULL;
+	u16_t old_friend;
+	u32_t poll_to;
+	int i;
 
 	if (buf->om_len < sizeof(*msg)) {
 		BT_WARN("Too short Friend Request");
 		return -EINVAL;
 	}
 
+	if (msg->recv_delay <= 0x09) {
+		BT_WARN("Prohibited ReceiveDelay (0x%02x)", msg->recv_delay);
+		return -EINVAL;
+	}
+
+	poll_to = (((u32_t)msg->poll_to[0] << 16) |
+		   ((u32_t)msg->poll_to[1] << 8) |
+		   ((u32_t)msg->poll_to[2]));
+
+	if (poll_to <= 0x000009 || poll_to >= 0x34bc00) {
+		BT_WARN("Prohibited PollTimeout (0x%06x)", poll_to);
+		return -EINVAL;
+	}
+
+	if (msg->num_elem == 0x00) {
+		BT_WARN("Prohibited NumElements value (0x00)");
+		return -EINVAL;
+	}
+
+	if (!MIN_QUEUE_SIZE_LOG(msg->criteria)) {
+		BT_WARN("Prohibited Minimum Queue Size in Friend Request");
+		return -EINVAL;
+	}
+
+	if (CONFIG_BT_MESH_FRIEND_QUEUE_SIZE < MIN_QUEUE_SIZE(msg->criteria)) {
+		BT_WARN("We have a too small Friend Queue size (%u < %u)",
+			CONFIG_BT_MESH_FRIEND_QUEUE_SIZE,
+			MIN_QUEUE_SIZE(msg->criteria));
+		return 0;
+	}
+
+	old_friend = sys_be16_to_cpu(msg->prev_addr);
+	if (BT_MESH_ADDR_IS_UNICAST(old_friend)) {
+		frnd = bt_mesh_friend_find(rx->sub->net_idx, old_friend,
+					   true, false);
+	} else {
+		frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr,
+					   true, false);
+	}
+
+	if (frnd) {
+		BT_WARN("Existing LPN re-requesting Friendship");
+		friend_clear(frnd);
+		goto init_friend;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
+		if (!bt_mesh.frnd[i].valid) {
+			frnd = &bt_mesh.frnd[i];
+			frnd->valid = 1;
+			break;
+		}
+	}
+
+	if (!frnd) {
+		BT_WARN("No free Friend contexts for new LPN");
+		return -ENOMEM;
+	}
+
+init_friend:
 	frnd->lpn = rx->ctx.addr;
-	frnd->rssi = rx->rssi;
+	frnd->net_idx = rx->sub->net_idx;
 	frnd->recv_delay = msg->recv_delay;
-	frnd->poll_to = (((u32_t)msg->poll_to[0] << 16) |
-			 ((u32_t)msg->poll_to[1] << 8) |
-			 ((u32_t)msg->poll_to[2]));
-	frnd->poll_to *= 100;
+	frnd->poll_to = poll_to * 100;
 	frnd->lpn_counter = sys_be16_to_cpu(msg->lpn_counter);
+	frnd->clear.frnd = sys_be16_to_cpu(msg->prev_addr);
 
 	BT_DBG("LPN 0x%04x rssi %d recv_delay %u poll_to %ums",
-	       frnd->lpn, frnd->rssi, frnd->recv_delay, frnd->poll_to);
+	       frnd->lpn, rx->rssi, frnd->recv_delay, frnd->poll_to);
+
+	if (BT_MESH_ADDR_IS_UNICAST(old_friend) &&
+	    !bt_mesh_elem_find(old_friend)) {
+		clear_procedure_start(frnd);
+	}
 
-	bt_mesh_friend_cred_add(sub->net_idx, sub->keys[0].net, 0,
-				frnd->lpn, frnd->lpn_counter, frnd->counter);
+	k_delayed_work_submit(&frnd->timer,
+			      offer_delay(frnd, rx->rssi, msg->criteria));
 
-	frnd->send_offer = 1;
+	friend_cred_create(rx->sub, frnd->lpn, frnd->lpn_counter,
+			   frnd->counter);
 
-	k_delayed_work_submit(&frnd->timer, K_MSEC(100));
+	enqueue_offer(frnd, rx->rssi);
 
 	return 0;
 }
 
-static void friend_timeout(struct os_event *work)
+static struct bt_mesh_friend_seg *get_seg(struct bt_mesh_friend *frnd,
+					  u16_t src, u64_t *seq_auth)
+{
+	struct bt_mesh_friend_seg *unassigned = NULL;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(frnd->seg); i++) {
+		struct bt_mesh_friend_seg *seg = &frnd->seg[i];
+		struct os_mbuf *buf = (void *)sys_slist_peek_head(&seg->queue);
+
+		if (buf && BT_MESH_ADV(buf)->addr == src &&
+		    FRIEND_ADV(buf)->seq_auth == *seq_auth) {
+			return seg;
+		}
+
+		if (!unassigned && !buf) {
+			unassigned = seg;
+		}
+	}
+
+	return unassigned;
+}
+
+static void enqueue_friend_pdu(struct bt_mesh_friend *frnd,
+			       enum bt_mesh_friend_pdu_type type,
+			       struct os_mbuf *buf)
 {
-	struct bt_mesh_friend *frnd = &bt_mesh.frnd;
+	struct bt_mesh_friend_seg *seg;
+	struct friend_adv *adv;
 
-	BT_DBG("send_offer %u send_update %u", frnd->send_offer,
-	       frnd->send_update);
+	BT_DBG("type %u", type);
 
-	if (frnd->send_offer) {
-		frnd->send_offer = 0;
-		send_friend_offer(frnd->rssi);
+	if (type == BT_MESH_FRIEND_PDU_SINGLE) {
+		if (frnd->sec_update) {
+			enqueue_update(frnd, 1);
+		}
+
+		enqueue_buf(frnd, buf);
 		return;
 	}
 
-	if (!frnd->send_update) {
-		BT_WARN("Friendship lost");
-		bt_mesh_friend_cred_del(bt_mesh.sub[0].net_idx, frnd->lpn);
+	adv = FRIEND_ADV(buf);
+	seg = get_seg(frnd, BT_MESH_ADV(buf)->addr, &adv->seq_auth);
+	if (!seg) {
+		BT_ERR("No free friend segment RX contexts for 0x%04x",
+		       BT_MESH_ADV(buf)->addr);
+		net_buf_unref(buf);
 		return;
 	}
 
-	frnd->send_update = 0;
+	net_buf_slist_put(&seg->queue, buf);
 
-	if (frnd->send_last && frnd->last) {
-		frnd->send_last = 0;
-		bt_mesh_adv_send(frnd->last, NULL);
-		return;
+	if (type == BT_MESH_FRIEND_PDU_COMPLETE) {
+		if (frnd->sec_update) {
+			enqueue_update(frnd, 1);
+		}
+
+		/* Only acks should have a valid SeqAuth in the Friend queue
+		 * (otherwise we can't easily detect them there), so clear
+		 * the SeqAuth information from the segments before merging.
+		 */
+		struct os_mbuf *m;
+		struct os_mbuf_pkthdr *mp;
+		SYS_SLIST_FOR_EACH_CONTAINER(&seg->queue, mp,) {
+			m = OS_MBUF_PKTHDR_TO_MBUF(mp);
+			FRIEND_ADV(m)->seq_auth = TRANS_SEQ_AUTH_NVAL;
+			frnd->queue_size++;
+		}
+
+		sys_slist_merge_slist(&frnd->queue, &seg->queue);
 	}
+}
 
-	if (frnd->last) {
+static void buf_send_start(u16_t duration, int err, void *user_data)
+{
+	struct bt_mesh_friend *frnd = user_data;
+
+	BT_DBG("err %d", err);
+
+	frnd->pending_buf = 0;
+
+	/* Friend Offer doesn't follow the re-sending semantics */
+	if (!frnd->established) {
 		net_buf_unref(frnd->last);
+		frnd->last = NULL;
 	}
+}
 
-	frnd->last = net_buf_get(&frnd->queue, K_NO_WAIT);
-	if (frnd->last) {
-		bt_mesh_adv_send(frnd->last, NULL);
+static void buf_send_end(int err, void *user_data)
+{
+	struct bt_mesh_friend *frnd = user_data;
+
+	BT_DBG("err %d", err);
+
+	if (frnd->pending_req) {
+		BT_WARN("Another request before previous completed sending");
+		return;
+	}
+
+	if (frnd->established) {
+		k_delayed_work_submit(&frnd->timer, frnd->poll_to);
+		BT_DBG("Waiting %u ms for next poll", frnd->poll_to);
 	} else {
-		send_friend_update();
+		/* Friend offer timeout is 1 second */
+		k_delayed_work_submit(&frnd->timer, K_SECONDS(1));
+		BT_DBG("Waiting for first poll");
 	}
+}
 
-	k_delayed_work_submit(&frnd->timer, frnd->poll_to);
+static void friend_timeout(struct os_event *work)
+{
+	struct bt_mesh_friend *frnd = work->ev_arg;
+	static const struct bt_mesh_send_cb buf_sent_cb = {
+		.start = buf_send_start,
+		.end = buf_send_end,
+	};
+
+	__ASSERT_NO_MSG(frnd->pending_buf == 0);
+
+	BT_DBG("lpn 0x%04x send_last %u last %p", frnd->lpn,
+	       frnd->send_last, frnd->last);
+
+	if (frnd->send_last && frnd->last) {
+		BT_DBG("Sending frnd->last %p", frnd->last);
+		frnd->send_last = 0;
+		goto send_last;
+	}
+
+	if (frnd->established && !frnd->pending_req) {
+		BT_WARN("Friendship lost with 0x%04x", frnd->lpn);
+		friend_clear(frnd);
+		return;
+	}
+
+	frnd->last = net_buf_slist_get(&frnd->queue);
+	if (!frnd->last) {
+		BT_WARN("Friendship not established with 0x%04x", frnd->lpn);
+		friend_clear(frnd);
+		return;
+	}
+
+	BT_DBG("Sending buf %p from Friend Queue of LPN 0x%04x",
+	       frnd->last, frnd->lpn);
+	frnd->queue_size--;
+
+send_last:
+	frnd->pending_req = 0;
+	frnd->pending_buf = 1;
+	bt_mesh_adv_send(frnd->last, &buf_sent_cb, frnd);
 }
 
 int bt_mesh_friend_init(void)
 {
-	struct bt_mesh_friend *frnd = &bt_mesh.frnd;
+	int rc;
+	int i;
+
+	rc = os_mempool_init(&friend_buf_mempool, FRIEND_BUF_COUNT,
+			BT_MESH_ADV_DATA_SIZE + BT_MESH_MBUF_HEADER_SIZE,
+			friend_buf_mem, "friend_buf_pool");
+	assert(rc == 0);
+
+	rc = os_mbuf_pool_init(&friend_os_mbuf_pool, &friend_buf_mempool,
+			BT_MESH_ADV_DATA_SIZE + BT_MESH_MBUF_HEADER_SIZE,
+			FRIEND_BUF_COUNT);
+	assert(rc == 0);
 
-	k_fifo_init(&frnd->queue);
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
+		struct bt_mesh_friend *frnd = &bt_mesh.frnd[i];
+		int j;
 
-	k_delayed_work_init(&frnd->timer, friend_timeout);
+		frnd->net_idx = BT_MESH_KEY_UNUSED;
+
+		sys_slist_init(&frnd->queue);
+
+		k_delayed_work_init(&frnd->timer, friend_timeout);
+		k_delayed_work_add_arg(&frnd->timer, frnd);
+		k_delayed_work_init(&frnd->clear.timer, clear_timeout);
+		k_delayed_work_add_arg(&frnd->clear.timer, frnd);
+
+		for (j = 0; j < ARRAY_SIZE(frnd->seg); j++) {
+			sys_slist_init(&frnd->seg[j].queue);
+		}
+	}
 
 	return 0;
 }
 
-bool bt_mesh_friend_enqueue(struct os_mbuf *buf, u16_t dst)
+static void friend_purge_old_ack(struct bt_mesh_friend *frnd, u64_t *seq_auth,
+				 u16_t src)
 {
-	/* FIXME: Add support for multiple LPNs and group addresses */
-	if (!bt_mesh_friend_dst_is_lpn(dst)) {
-		return false;
+	sys_snode_t *cur, *prev = NULL;
+
+	BT_DBG("SeqAuth %llx src 0x%04x", *seq_auth, src);
+
+	for (cur = sys_slist_peek_head(&frnd->queue);
+	     cur != NULL; prev = cur, cur = sys_slist_peek_next(cur)) {
+		struct os_mbuf *buf = (void *)cur;
+
+		if (BT_MESH_ADV(buf)->addr == src &&
+		    FRIEND_ADV(buf)->seq_auth == *seq_auth) {
+			BT_DBG("Removing old ack from Friend Queue");
+
+			sys_slist_remove(&frnd->queue, prev, cur);
+			frnd->queue_size--;
+
+			net_buf_unref(buf);
+			break;
+		}
+	}
+}
+
+static void friend_lpn_enqueue_rx(struct bt_mesh_friend *frnd,
+				  struct bt_mesh_net_rx *rx,
+				  enum bt_mesh_friend_pdu_type type,
+				  u64_t *seq_auth, struct os_mbuf *sbuf)
+{
+	struct friend_pdu_info info;
+	struct os_mbuf *buf;
+
+	BT_DBG("LPN 0x%04x queue_size %u", frnd->lpn, frnd->queue_size);
+
+	if (type == BT_MESH_FRIEND_PDU_SINGLE && seq_auth) {
+		friend_purge_old_ack(frnd, seq_auth, rx->ctx.addr);
 	}
 
-	if (BT_MESH_ADDR_IS_UNICAST(dst)) {
-		net_buf_put(&bt_mesh.frnd.queue, net_buf_ref(buf));
+	info.src = rx->ctx.addr;
+	info.dst = rx->dst;
+
+	if (rx->net_if == BT_MESH_NET_IF_LOCAL) {
+		info.ttl = rx->ctx.recv_ttl;
 	} else {
-		struct os_mbuf *clone = net_buf_clone(buf, K_NO_WAIT);
+		info.ttl = rx->ctx.recv_ttl - 1;
+	}
+
+	info.ctl = rx->ctl;
+
+	info.seq[0] = (rx->seq >> 16);
+	info.seq[1] = (rx->seq >> 8);
+	info.seq[2] = rx->seq;
+
+	info.iv_index = BT_MESH_NET_IVI_RX(rx);
+
+	buf = create_friend_pdu(frnd, &info, sbuf);
+	if (!buf) {
+		BT_ERR("Failed to encode Friend buffer");
+		return;
+	}
+
+	if (seq_auth) {
+		FRIEND_ADV(buf)->seq_auth = *seq_auth;
+	}
+
+	enqueue_friend_pdu(frnd, type, buf);
+
+	BT_DBG("Queued message for LPN 0x%04x, queue_size %u",
+	       frnd->lpn, frnd->queue_size);
+}
+
+static void friend_lpn_enqueue_tx(struct bt_mesh_friend *frnd,
+				  struct bt_mesh_net_tx *tx,
+				  enum bt_mesh_friend_pdu_type type,
+				  u64_t *seq_auth, struct os_mbuf *sbuf)
+{
+	struct friend_pdu_info info;
+	struct os_mbuf *buf;
+
+	BT_DBG("LPN 0x%04x", frnd->lpn);
+
+	if (type == BT_MESH_FRIEND_PDU_SINGLE && seq_auth) {
+		friend_purge_old_ack(frnd, seq_auth, tx->src);
+	}
+
+	info.src = tx->src;
+	info.dst = tx->ctx->addr;
+
+	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++;
+
+	info.iv_index = BT_MESH_NET_IVI_TX;
+
+	buf = create_friend_pdu(frnd, &info, sbuf);
+	if (!buf) {
+		BT_ERR("Failed to encode Friend buffer");
+		return;
+	}
+
+	if (seq_auth) {
+		FRIEND_ADV(buf)->seq_auth = *seq_auth;
+	}
+
+	enqueue_friend_pdu(frnd, type, buf);
 
-		if (clone) {
-			net_buf_put(&bt_mesh.frnd.queue, clone);
-		} else {
-			BT_WARN("Unable to allocate buffer for friend queue");
-			return false;
+	BT_DBG("Queued message for LPN 0x%04x", frnd->lpn);
+}
+
+static bool friend_lpn_matches(struct bt_mesh_friend *frnd, u16_t net_idx,
+			       u16_t addr)
+{
+	int i;
+
+	if (!frnd->established) {
+		return false;
+	}
+
+	if (net_idx != frnd->net_idx) {
+		return false;
+	}
+
+	if (BT_MESH_ADDR_IS_UNICAST(addr)) {
+		if (addr == frnd->lpn) {
+			return true;
+		}
+
+		return false;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(frnd->sub_list); i++) {
+		if (frnd->sub_list[i] == addr) {
+			return true;
 		}
 	}
 
-	BT_DBG("Queued message for LPN");
+	return false;
+}
+
+bool bt_mesh_friend_match(u16_t net_idx, u16_t addr)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
+		struct bt_mesh_friend *frnd = &bt_mesh.frnd[i];
+
+		if (friend_lpn_matches(frnd, net_idx, addr)) {
+			BT_DBG("LPN 0x%04x matched address 0x%04x",
+			       frnd->lpn, addr);
+			return true;
+		}
+	}
+
+	BT_DBG("No matching LPN for address 0x%04x", addr);
+
+	return false;
+}
+
+void bt_mesh_friend_enqueue_rx(struct bt_mesh_net_rx *rx,
+			       enum bt_mesh_friend_pdu_type type,
+			       u64_t *seq_auth, struct os_mbuf *sbuf)
+{
+	int i;
+
+	if (!rx->friend_match ||
+	    (rx->ctx.recv_ttl <= 1 && rx->net_if != BT_MESH_NET_IF_LOCAL) ||
+	    bt_mesh_friend_get() != BT_MESH_FRIEND_ENABLED) {
+		return;
+	}
+
+	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);
 
-	return true;
+	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)) {
+			friend_lpn_enqueue_rx(frnd, rx, type, seq_auth, sbuf);
+		}
+	}
+}
+
+bool bt_mesh_friend_enqueue_tx(struct bt_mesh_net_tx *tx,
+			       enum bt_mesh_friend_pdu_type type,
+			       u64_t *seq_auth, struct os_mbuf *sbuf)
+{
+	bool matched = false;
+	int i;
+
+	if (!bt_mesh_friend_match(tx->sub->net_idx, tx->ctx->addr) ||
+	    bt_mesh_friend_get() != BT_MESH_FRIEND_ENABLED) {
+		return matched;
+	}
+
+	BT_DBG("net_idx 0x%04x dst 0x%04x src 0x%04x", tx->sub->net_idx,
+	       tx->ctx->addr, tx->src);
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
+		struct bt_mesh_friend *frnd = &bt_mesh.frnd[i];
+
+		if (friend_lpn_matches(frnd, tx->sub->net_idx, tx->ctx->addr)) {
+			friend_lpn_enqueue_tx(frnd, tx, type, seq_auth, sbuf);
+			matched = true;
+		}
+	}
+
+	return matched;
+}
+
+void bt_mesh_friend_clear_incomplete(struct bt_mesh_subnet *sub, u16_t src,
+				     u16_t dst, u64_t *seq_auth)
+{
+	int i;
+
+	BT_DBG("");
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) {
+		struct bt_mesh_friend *frnd = &bt_mesh.frnd[i];
+		int j;
+
+		if (!friend_lpn_matches(frnd, sub->net_idx, dst)) {
+			continue;
+		}
+
+		for (j = 0; j < ARRAY_SIZE(frnd->seg); j++) {
+			struct bt_mesh_friend_seg *seg = &frnd->seg[j];
+			struct os_mbuf *buf;
+
+			buf = (void *)sys_slist_peek_head(&seg->queue);
+			if (!buf) {
+				continue;
+			}
+
+			if (BT_MESH_ADV(buf)->addr != src) {
+				continue;
+			}
+
+			if (FRIEND_ADV(buf)->seq_auth != *seq_auth) {
+				continue;
+			}
+
+			BT_WARN("Clearing incomplete segments for 0x%04x", src);
+
+			while (!sys_slist_is_empty(&seg->queue)) {
+				net_buf_unref(net_buf_slist_get(&seg->queue));
+			}
+		}
+	}
 }
 
 #endif //MYNEWT_VAL(BLE_MESH_FRIEND)
diff --git a/net/nimble/host/mesh/src/friend.h b/net/nimble/host/mesh/src/friend.h
index e3b423866..053de146c 100644
--- a/net/nimble/host/mesh/src/friend.h
+++ b/net/nimble/host/mesh/src/friend.h
@@ -11,25 +11,41 @@
 
 #include "mesh/mesh.h"
 
-static inline bool
-bt_mesh_friend_dst_is_lpn(u16_t dst)
-{
-#if (MYNEWT_VAL(BLE_MESH_FRIEND))
-    return (dst == bt_mesh.frnd.lpn);
-#else
-    return false;
-#endif
-}
-
-bool
-bt_mesh_friend_enqueue(struct os_mbuf *buf, u16_t dst);
-
-int
-bt_mesh_friend_poll(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
-int
-bt_mesh_friend_req(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
-
-int
-bt_mesh_friend_init(void);
+enum bt_mesh_friend_pdu_type {
+	BT_MESH_FRIEND_PDU_SINGLE,
+	BT_MESH_FRIEND_PDU_PARTIAL,
+	BT_MESH_FRIEND_PDU_COMPLETE,
+};
+
+bool bt_mesh_friend_match(u16_t net_idx, u16_t addr);
+
+struct bt_mesh_friend *bt_mesh_friend_find(u16_t net_idx, u16_t lpn_addr,
+					   bool valid, bool established);
+
+void bt_mesh_friend_enqueue_rx(struct bt_mesh_net_rx *rx,
+			       enum bt_mesh_friend_pdu_type type,
+			       u64_t *seq_auth, struct os_mbuf *sbuf);
+bool bt_mesh_friend_enqueue_tx(struct bt_mesh_net_tx *tx,
+			       enum bt_mesh_friend_pdu_type type,
+			       u64_t *seq_auth, struct os_mbuf *sbuf);
+
+void bt_mesh_friend_clear_incomplete(struct bt_mesh_subnet *sub, u16_t src,
+				     u16_t dst, u64_t *seq_auth);
+
+void bt_mesh_friend_sec_update(u16_t net_idx);
+
+void bt_mesh_friend_clear_net_idx(u16_t net_idx);
+
+int bt_mesh_friend_poll(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
+int bt_mesh_friend_req(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
+int bt_mesh_friend_clear(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
+int bt_mesh_friend_clear_cfm(struct bt_mesh_net_rx *rx,
+			     struct os_mbuf *buf);
+int bt_mesh_friend_sub_add(struct bt_mesh_net_rx *rx,
+			   struct os_mbuf *buf);
+int bt_mesh_friend_sub_rem(struct bt_mesh_net_rx *rx,
+			   struct os_mbuf *buf);
+
+int bt_mesh_friend_init(void);
 
 #endif
diff --git a/net/nimble/host/mesh/src/glue.c b/net/nimble/host/mesh/src/glue.c
index 39a53eb4d..9ce1319e9 100644
--- a/net/nimble/host/mesh/src/glue.c
+++ b/net/nimble/host/mesh/src/glue.c
@@ -19,6 +19,7 @@
 
 #include "mesh/glue.h"
 #include "adv.h"
+#define BT_DBG_ENABLED (MYNEWT_VAL(BLE_MESH_DEBUG))
 
 extern u8_t g_mesh_addr_type;
 
@@ -172,6 +173,7 @@ net_buf_simple_add_le16(struct os_mbuf *om, uint16_t val)
 {
     val = htole16(val);
     os_mbuf_append(om, &val, sizeof(val));
+    ASSERT_NOT_CHAIN(om);
 }
 
 void
@@ -179,6 +181,7 @@ net_buf_simple_add_be16(struct os_mbuf *om, uint16_t val)
 {
     val = htobe16(val);
     os_mbuf_append(om, &val, sizeof(val));
+    ASSERT_NOT_CHAIN(om);
 }
 
 void
@@ -186,12 +189,14 @@ net_buf_simple_add_be32(struct os_mbuf *om, uint32_t val)
 {
     val = htobe32(val);
     os_mbuf_append(om, &val, sizeof(val));
+    ASSERT_NOT_CHAIN(om);
 }
 
 void
 net_buf_simple_add_u8(struct os_mbuf *om, uint8_t val)
 {
     os_mbuf_append(om, &val, 1);
+    ASSERT_NOT_CHAIN(om);
 }
 
 void
@@ -207,6 +212,7 @@ net_buf_simple_push_le16(struct os_mbuf *om, uint16_t val)
     if (om->om_pkthdr_len) {
         OS_MBUF_PKTHDR(om)->omp_len += 2;
     }
+    ASSERT_NOT_CHAIN(om);
 }
 
 void
@@ -222,6 +228,7 @@ net_buf_simple_push_be16(struct os_mbuf *om, uint16_t val)
     if (om->om_pkthdr_len) {
         OS_MBUF_PKTHDR(om)->omp_len += 2;
     }
+    ASSERT_NOT_CHAIN(om);
 }
 
 void
@@ -237,6 +244,7 @@ net_buf_simple_push_u8(struct os_mbuf *om, uint8_t val)
     if (om->om_pkthdr_len) {
         OS_MBUF_PKTHDR(om)->omp_len += 1;
     }
+    ASSERT_NOT_CHAIN(om);
 }
 
 void
@@ -251,6 +259,7 @@ net_buf_add_zeros(struct os_mbuf *om, uint8_t len)
     if(rc) {
         assert(0);
     }
+    ASSERT_NOT_CHAIN(om);
 }
 
 void *
@@ -263,7 +272,12 @@ net_buf_simple_pull(struct os_mbuf *om, uint8_t len)
 void*
 net_buf_simple_add(struct os_mbuf *om, uint8_t len)
 {
-    return os_mbuf_extend(om, len);
+    void * tmp;
+
+    tmp = os_mbuf_extend(om, len);
+    ASSERT_NOT_CHAIN(om);
+
+    return tmp;
 }
 
 bool
diff --git a/net/nimble/host/mesh/src/health_cli.c b/net/nimble/host/mesh/src/health_cli.c
new file mode 100644
index 000000000..77435eceb
--- /dev/null
+++ b/net/nimble/host/mesh/src/health_cli.c
@@ -0,0 +1,514 @@
+/*  Bluetooth Mesh */
+
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+
+#include "syscfg/syscfg.h"
+#define BT_DBG_ENABLED (MYNEWT_VAL(BLE_MESH_DEBUG_MODEL))
+#include "host/ble_hs_log.h"
+
+#include "mesh/mesh.h"
+#include "mesh_priv.h"
+#include "adv.h"
+#include "net.h"
+#include "transport.h"
+#include "access.h"
+#include "foundation.h"
+
+static s32_t msg_timeout = K_SECONDS(2);
+
+static struct bt_mesh_health_cli *health_cli;
+
+struct health_fault_param {
+	u16_t   cid;
+	u8_t   *expect_test_id;
+	u8_t   *test_id;
+	u8_t   *faults;
+	size_t *fault_count;
+};
+
+static void health_fault_status(struct bt_mesh_model *model,
+				struct bt_mesh_msg_ctx *ctx,
+				struct os_mbuf *buf)
+{
+	struct health_fault_param *param;
+	u8_t test_id;
+	u16_t cid;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (health_cli->op_pending != OP_HEALTH_FAULT_STATUS) {
+		BT_WARN("Unexpected Health Fault Status message");
+		return;
+	}
+
+	param = health_cli->op_param;
+
+	test_id = net_buf_simple_pull_u8(buf);
+	if (param->expect_test_id && test_id != *param->expect_test_id) {
+		BT_WARN("Health fault with unexpected Test ID");
+		return;
+	}
+
+	cid = net_buf_simple_pull_le16(buf);
+	if (cid != param->cid) {
+		BT_WARN("Health fault with unexpected Company ID");
+		return;
+	}
+
+	if (param->test_id) {
+		*param->test_id = test_id;
+	}
+
+	if (buf->om_len > *param->fault_count) {
+		BT_WARN("Got more faults than there's space for");
+	} else {
+		*param->fault_count = buf->om_len;
+	}
+
+	memcpy(param->faults, buf->om_data, *param->fault_count);
+
+	k_sem_give(&health_cli->op_sync);
+}
+
+static void health_current_status(struct bt_mesh_model *model,
+				  struct bt_mesh_msg_ctx *ctx,
+				  struct os_mbuf *buf)
+{
+	struct bt_mesh_health_cli *cli = model->user_data;
+	u8_t test_id;
+	u16_t cid;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	test_id = net_buf_simple_pull_u8(buf);
+	cid = net_buf_simple_pull_le16(buf);
+
+	BT_DBG("Test ID 0x%02x Company ID 0x%04x Fault Count %u",
+	       test_id, cid, buf->om_len);
+
+	if (!cli->current_status) {
+		BT_WARN("No Current Status callback available");
+		return;
+	}
+
+	cli->current_status(cli, ctx->addr, test_id, cid, buf->om_data, buf->om_len);
+}
+
+struct health_period_param {
+	u8_t *divisor;
+};
+
+static void health_period_status(struct bt_mesh_model *model,
+				 struct bt_mesh_msg_ctx *ctx,
+				 struct os_mbuf *buf)
+{
+	struct health_period_param *param;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (health_cli->op_pending != OP_HEALTH_PERIOD_STATUS) {
+		BT_WARN("Unexpected Health Period Status message");
+		return;
+	}
+
+	param = health_cli->op_param;
+
+	*param->divisor = net_buf_simple_pull_u8(buf);
+
+	k_sem_give(&health_cli->op_sync);
+}
+
+struct health_attention_param {
+	u8_t *attention;
+};
+
+static void health_attention_status(struct bt_mesh_model *model,
+				    struct bt_mesh_msg_ctx *ctx,
+				    struct os_mbuf *buf)
+{
+	struct health_attention_param *param;
+
+	BT_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: %s",
+	       ctx->net_idx, ctx->app_idx, ctx->addr, buf->om_len,
+	       bt_hex(buf->om_data, buf->om_len));
+
+	if (health_cli->op_pending != OP_ATTENTION_STATUS) {
+		BT_WARN("Unexpected Health Attention Status message");
+		return;
+	}
+
+	param = health_cli->op_param;
+
+	*param->attention = net_buf_simple_pull_u8(buf);
+
+	k_sem_give(&health_cli->op_sync);
+}
+
+const struct bt_mesh_model_op bt_mesh_health_cli_op[] = {
+	{ OP_HEALTH_FAULT_STATUS,    3,   health_fault_status },
+	{ OP_HEALTH_CURRENT_STATUS,  3,   health_current_status },
+	{ OP_HEALTH_PERIOD_STATUS,   1,   health_period_status },
+	{ OP_ATTENTION_STATUS,       1,   health_attention_status },
+	BT_MESH_MODEL_OP_END,
+};
+
+static int check_cli(void)
+{
+	if (!health_cli) {
+		BT_ERR("No available Health Client context!");
+		return -EINVAL;
+	}
+
+	if (health_cli->op_pending) {
+		BT_WARN("Another synchronous operation pending");
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static int cli_wait(void *param, u32_t op)
+{
+	int err;
+
+	health_cli->op_param = param;
+	health_cli->op_pending = op;
+
+	err = k_sem_take(&health_cli->op_sync, msg_timeout);
+
+	health_cli->op_pending = 0;
+	health_cli->op_param = NULL;
+
+	return err;
+}
+
+int bt_mesh_health_attention_get(u16_t net_idx, u16_t addr, u16_t app_idx,
+				 u8_t *attention)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 0 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = app_idx,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct health_attention_param param = {
+		.attention = attention,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_ATTENTION_GET);
+
+	err = bt_mesh_model_send(health_cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	return cli_wait(&param, OP_ATTENTION_STATUS);
+}
+
+int bt_mesh_health_attention_set(u16_t net_idx, u16_t addr, u16_t app_idx,
+				 u8_t attention, u8_t *updated_attention)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 1 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = app_idx,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct health_attention_param param = {
+		.attention = updated_attention,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	if (updated_attention) {
+		bt_mesh_model_msg_init(msg, OP_ATTENTION_SET);
+	} else {
+		bt_mesh_model_msg_init(msg, OP_ATTENTION_SET_UNREL);
+	}
+
+	net_buf_simple_add_u8(msg, attention);
+
+	err = bt_mesh_model_send(health_cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!updated_attention) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_ATTENTION_STATUS);
+}
+
+int bt_mesh_health_period_get(u16_t net_idx, u16_t addr, u16_t app_idx,
+			      u8_t *divisor)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 0 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = app_idx,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct health_period_param param = {
+		.divisor = divisor,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_HEALTH_PERIOD_GET);
+
+	err = bt_mesh_model_send(health_cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	return cli_wait(&param, OP_HEALTH_PERIOD_STATUS);
+}
+
+int bt_mesh_health_period_set(u16_t net_idx, u16_t addr, u16_t app_idx,
+			      u8_t divisor, u8_t *updated_divisor)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 1 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = app_idx,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct health_period_param param = {
+		.divisor = updated_divisor,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	if (updated_divisor) {
+		bt_mesh_model_msg_init(msg, OP_HEALTH_PERIOD_SET);
+	} else {
+		bt_mesh_model_msg_init(msg, OP_HEALTH_PERIOD_SET_UNREL);
+	}
+
+	net_buf_simple_add_u8(msg, divisor);
+
+	err = bt_mesh_model_send(health_cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!updated_divisor) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_HEALTH_PERIOD_STATUS);
+}
+
+int bt_mesh_health_fault_test(u16_t net_idx, u16_t addr, u16_t app_idx,
+			      u16_t cid, u8_t test_id, u8_t *faults,
+			      size_t *fault_count)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 3 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = app_idx,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct health_fault_param param = {
+		.cid = cid,
+		.expect_test_id = &test_id,
+		.faults = faults,
+		.fault_count = fault_count,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	if (faults) {
+		bt_mesh_model_msg_init(msg, OP_HEALTH_FAULT_TEST);
+	} else {
+		bt_mesh_model_msg_init(msg, OP_HEALTH_FAULT_TEST_UNREL);
+	}
+
+	net_buf_simple_add_u8(msg, test_id);
+	net_buf_simple_add_le16(msg, cid);
+
+	err = bt_mesh_model_send(health_cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!faults) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_HEALTH_FAULT_STATUS);
+}
+
+int bt_mesh_health_fault_clear(u16_t net_idx, u16_t addr, u16_t app_idx,
+			       u16_t cid, u8_t *test_id, u8_t *faults,
+			       size_t *fault_count)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 2 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = app_idx,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct health_fault_param param = {
+		.cid = cid,
+		.test_id = test_id,
+		.faults = faults,
+		.fault_count = fault_count,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	if (test_id) {
+		bt_mesh_model_msg_init(msg, OP_HEALTH_FAULT_CLEAR);
+	} else {
+		bt_mesh_model_msg_init(msg, OP_HEALTH_FAULT_CLEAR_UNREL);
+	}
+
+	net_buf_simple_add_le16(msg, cid);
+
+	err = bt_mesh_model_send(health_cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	if (!test_id) {
+		return 0;
+	}
+
+	return cli_wait(&param, OP_HEALTH_FAULT_STATUS);
+}
+
+int bt_mesh_health_fault_get(u16_t net_idx, u16_t addr, u16_t app_idx,
+			     u16_t cid, u8_t *test_id, u8_t *faults,
+			     size_t *fault_count)
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 2 + 4);
+	struct bt_mesh_msg_ctx ctx = {
+		.net_idx = net_idx,
+		.app_idx = app_idx,
+		.addr = addr,
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+	};
+	struct health_fault_param param = {
+		.cid = cid,
+		.test_id = test_id,
+		.faults = faults,
+		.fault_count = fault_count,
+	};
+	int err;
+
+	err = check_cli();
+	if (err) {
+		return err;
+	}
+
+	bt_mesh_model_msg_init(msg, OP_HEALTH_FAULT_GET);
+	net_buf_simple_add_le16(msg, cid);
+
+	err = bt_mesh_model_send(health_cli->model, &ctx, msg, NULL, NULL);
+	if (err) {
+		BT_ERR("model_send() failed (err %d)", err);
+		return err;
+	}
+
+	return cli_wait(&param, OP_HEALTH_FAULT_STATUS);
+}
+
+s32_t bt_mesh_health_cli_timeout_get(void)
+{
+	return msg_timeout;
+}
+
+void bt_mesh_health_cli_timeout_set(s32_t timeout)
+{
+	msg_timeout = timeout;
+}
+
+int bt_mesh_health_cli_set(struct bt_mesh_model *model)
+{
+	if (!model->user_data) {
+		BT_ERR("No Health Client context for given model");
+		return -EINVAL;
+	}
+
+	health_cli = model->user_data;
+
+	return 0;
+}
+
+int bt_mesh_health_cli_init(struct bt_mesh_model *model, bool primary)
+{
+	struct bt_mesh_health_cli *cli = model->user_data;
+
+	BT_DBG("primary %u", primary);
+
+	if (!cli) {
+		BT_ERR("No Health Client context provided");
+		return -EINVAL;
+	}
+
+	cli = model->user_data;
+	cli->model = model;
+
+	k_sem_init(&cli->op_sync, 0, 1);
+
+	/* Set the default health client pointer */
+	if (!health_cli) {
+		health_cli = cli;
+	}
+
+	return 0;
+}
diff --git a/net/nimble/host/mesh/src/health.c b/net/nimble/host/mesh/src/health_srv.c
similarity index 68%
rename from net/nimble/host/mesh/src/health.c
rename to net/nimble/host/mesh/src/health_srv.c
index 189bd0b78..642e6163d 100644
--- a/net/nimble/host/mesh/src/health.c
+++ b/net/nimble/host/mesh/src/health_srv.c
@@ -24,27 +24,15 @@
 
 #define HEALTH_TEST_STANDARD 0x00
 
-/* Increasing this requires also increasing the system workqueue */
-#define MAX_FAULTS 32
-#define HEALTH_STATUS_SIZE_MAX (1 + 3 + MAX_FAULTS + 4)
-
-#if BT_MESH_TX_SDU_MAX < HEALTH_STATUS_SIZE_MAX
-#define HEALTH_STATUS_SIZE BT_MESH_TX_SDU_MAX
-#else
-#define HEALTH_STATUS_SIZE HEALTH_STATUS_SIZE_MAX
-#endif
-
 /* Health Server context of the primary element */
-struct bt_mesh_health *health_srv;
+struct bt_mesh_health_srv *health_srv;
 
 static void health_get_registered(struct bt_mesh_model *mod,
 				  u16_t company_id,
 				  struct os_mbuf *msg)
 {
-	struct bt_mesh_health *srv = mod->user_data;
-	u8_t fault_count;
+	struct bt_mesh_health_srv *srv = mod->user_data;
 	u8_t *test_id;
-	int err;
 
 	BT_DBG("Company ID 0x%04x", company_id);
 
@@ -52,12 +40,14 @@ static void health_get_registered(struct bt_mesh_model *mod,
 
 	test_id = net_buf_simple_add(msg, 1);
 	net_buf_simple_add_le16(msg, company_id);
-	fault_count = net_buf_simple_tailroom(msg) - 4;
 
-	if (srv->fault_get_reg) {
-		err = srv->fault_get_reg(mod, company_id, test_id,
-					 net_buf_simple_tail(msg),
-					 &fault_count);
+	if (srv->cb && srv->cb->fault_get_reg) {
+		u8_t fault_count = net_buf_simple_tailroom(msg) - 4;
+		int err;
+
+		err = srv->cb->fault_get_reg(mod, company_id, test_id,
+					     net_buf_simple_tail(msg),
+					     &fault_count);
 		if (err) {
 			BT_ERR("Failed to get faults (err %d)", err);
 			*test_id = HEALTH_TEST_STANDARD;
@@ -73,7 +63,7 @@ static void health_get_registered(struct bt_mesh_model *mod,
 static size_t health_get_current(struct bt_mesh_model *mod,
 				 struct os_mbuf *msg)
 {
-	struct bt_mesh_health *srv = mod->user_data;
+	struct bt_mesh_health_srv *srv = mod->user_data;
 	const struct bt_mesh_comp *comp;
 	u8_t *test_id, *company_ptr;
 	u16_t company_id;
@@ -86,12 +76,11 @@ static size_t health_get_current(struct bt_mesh_model *mod,
 	company_ptr = net_buf_simple_add(msg, sizeof(company_id));
 	comp = bt_mesh_comp_get();
 
-	fault_count = net_buf_simple_tailroom(msg) - 4;
-
-	if (srv->fault_get_cur) {
-		err = srv->fault_get_cur(mod, test_id, &company_id,
-					 net_buf_simple_tail(msg),
-					 &fault_count);
+	if (srv->cb && srv->cb->fault_get_cur) {
+		fault_count = net_buf_simple_tailroom(msg);
+		err = srv->cb->fault_get_cur(mod, test_id, &company_id,
+					     net_buf_simple_tail(msg),
+					     &fault_count);
 		if (err) {
 			BT_ERR("Failed to get faults (err %d)", err);
 			sys_put_le16(comp->cid, company_ptr);
@@ -115,35 +104,35 @@ static void health_fault_get(struct bt_mesh_model *model,
 			     struct bt_mesh_msg_ctx *ctx,
 			     struct os_mbuf *buf)
 {
-	struct os_mbuf *msg = NET_BUF_SIMPLE(HEALTH_STATUS_SIZE);
+	struct os_mbuf *sdu = NET_BUF_SIMPLE(BT_MESH_TX_SDU_MAX);
 	u16_t company_id;
 
 	company_id = net_buf_simple_pull_le16(buf);
 
 	BT_DBG("company_id 0x%04x", company_id);
 
-	health_get_registered(model, company_id, msg);
+	health_get_registered(model, company_id, sdu);
 
-	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+	if (bt_mesh_model_send(model, ctx, sdu, NULL, NULL)) {
 		BT_ERR("Unable to send Health Current Status response");
 	}
 
-	os_mbuf_free_chain(msg);
+	os_mbuf_free_chain(sdu);
 }
 
 static void health_fault_clear_unrel(struct bt_mesh_model *model,
 				     struct bt_mesh_msg_ctx *ctx,
 				     struct os_mbuf *buf)
 {
-	struct bt_mesh_health *srv = model->user_data;
+	struct bt_mesh_health_srv *srv = model->user_data;
 	u16_t company_id;
 
 	company_id = net_buf_simple_pull_le16(buf);
 
 	BT_DBG("company_id 0x%04x", company_id);
 
-	if (srv->fault_clear) {
-		srv->fault_clear(model, company_id);
+	if (srv->cb && srv->cb->fault_clear) {
+		srv->cb->fault_clear(model, company_id);
 	}
 }
 
@@ -151,33 +140,32 @@ static void health_fault_clear(struct bt_mesh_model *model,
 			       struct bt_mesh_msg_ctx *ctx,
 			       struct os_mbuf *buf)
 {
-	struct os_mbuf *msg = NET_BUF_SIMPLE(HEALTH_STATUS_SIZE);
-	struct bt_mesh_health *srv = model->user_data;
+	struct os_mbuf *sdu = NET_BUF_SIMPLE(BT_MESH_TX_SDU_MAX);
+	struct bt_mesh_health_srv *srv = model->user_data;
 	u16_t company_id;
 
 	company_id = net_buf_simple_pull_le16(buf);
 
 	BT_DBG("company_id 0x%04x", company_id);
 
-	if (srv->fault_clear) {
-		srv->fault_clear(model, company_id);
+	if (srv->cb && srv->cb->fault_clear) {
+		srv->cb->fault_clear(model, company_id);
 	}
 
-	health_get_registered(model, company_id, msg);
+	health_get_registered(model, company_id, sdu);
 
-	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+	if (bt_mesh_model_send(model, ctx, sdu, NULL, NULL)) {
 		BT_ERR("Unable to send Health Current Status response");
 	}
 
-	os_mbuf_free_chain(msg);
+	os_mbuf_free_chain(sdu);
 }
 
 static void health_fault_test_unrel(struct bt_mesh_model *model,
 				    struct bt_mesh_msg_ctx *ctx,
 				    struct os_mbuf *buf)
 {
-	struct bt_mesh_health *srv = model->user_data;
-	const struct bt_mesh_comp *comp;
+	struct bt_mesh_health_srv *srv = model->user_data;
 	u16_t company_id;
 	u8_t test_id;
 
@@ -186,15 +174,8 @@ static void health_fault_test_unrel(struct bt_mesh_model *model,
 
 	BT_DBG("test 0x%02x company 0x%04x", test_id, company_id);
 
-	comp = bt_mesh_comp_get();
-	if (comp->cid != company_id) {
-		BT_WARN("CID 0x%04x doesn't match composition CID 0x%04x",
-			company_id, comp->cid);
-		return;
-	}
-
-	if (srv->fault_test) {
-		srv->fault_test(model, test_id, company_id);
+	if (srv->cb && srv->cb->fault_test) {
+		srv->cb->fault_test(model, test_id, company_id);
 	}
 }
 
@@ -202,11 +183,10 @@ static void health_fault_test(struct bt_mesh_model *model,
 			      struct bt_mesh_msg_ctx *ctx,
 			      struct os_mbuf *buf)
 {
-	struct os_mbuf *msg = NET_BUF_SIMPLE(HEALTH_STATUS_SIZE);
-	struct bt_mesh_health *srv = model->user_data;
+	struct os_mbuf *sdu = NET_BUF_SIMPLE(BT_MESH_TX_SDU_MAX);
+	struct bt_mesh_health_srv *srv = model->user_data;
 	u16_t company_id;
 	u8_t test_id;
-	int rc;
 
 	BT_DBG("");
 
@@ -215,22 +195,24 @@ static void health_fault_test(struct bt_mesh_model *model,
 
 	BT_DBG("test 0x%02x company 0x%04x", test_id, company_id);
 
-	if (srv->fault_test) {
-		rc = srv->fault_test(model, test_id, company_id);
-		if (rc) {
-			BT_WARN("Running fault test failed with err %d", rc);
+	if (srv->cb && srv->cb->fault_test) {
+		int err;
+
+		err = srv->cb->fault_test(model, test_id, company_id);
+		if (err) {
+			BT_WARN("Running fault test failed with err %d", err);
 			goto done;
 		}
 	}
 
-	health_get_registered(model, company_id, msg);
+	health_get_registered(model, company_id, sdu);
 
-	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+	if (bt_mesh_model_send(model, ctx, sdu, NULL, NULL)) {
 		BT_ERR("Unable to send Health Current Status response");
 	}
 
 done:
-	os_mbuf_free_chain(msg);
+	os_mbuf_free_chain(sdu);
 }
 
 static void send_attention_status(struct bt_mesh_model *model,
@@ -238,17 +220,20 @@ static void send_attention_status(struct bt_mesh_model *model,
 {
 	/* Needed size: opcode (2 bytes) + msg + MIC */
 	struct os_mbuf *msg = NET_BUF_SIMPLE(2 + 1 + 4);
-	struct bt_mesh_health *srv = model->user_data;
+	struct bt_mesh_health_srv *srv = model->user_data;
 	u8_t time;
 
-	time = k_delayed_work_remaining_get(&srv->attention.timer) / 1000;
+	time = k_delayed_work_remaining_get(&srv->attn_timer) / 1000;
 	BT_DBG("%u second%s", time, (time == 1) ? "" : "s");
 
 	bt_mesh_model_msg_init(msg, OP_ATTENTION_STATUS);
 
 	net_buf_simple_add_u8(msg, time);
 
-	bt_mesh_model_send(model, ctx, msg, NULL, NULL);
+	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+		BT_ERR("Unable to send Attention Status");
+	}
+
 	os_mbuf_free_chain(msg);
 }
 
@@ -295,7 +280,10 @@ static void send_health_period_status(struct bt_mesh_model *model,
 
 	net_buf_simple_add_u8(msg, model->pub->period_div);
 
-	bt_mesh_model_send(model, ctx, msg, NULL, NULL);
+	if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
+		BT_ERR("Unable to send Health Period Status");
+	}
+
 	os_mbuf_free_chain(msg);
 }
 
@@ -336,7 +324,7 @@ static void health_period_set(struct bt_mesh_model *model,
 	send_health_period_status(model, ctx);
 }
 
-const struct bt_mesh_model_op bt_mesh_health_op[] = {
+const struct bt_mesh_model_op bt_mesh_health_srv_op[] = {
 	{ OP_HEALTH_FAULT_GET,         2,   health_fault_get },
 	{ OP_HEALTH_FAULT_CLEAR,       2,   health_fault_clear },
 	{ OP_HEALTH_FAULT_CLEAR_UNREL, 2,   health_fault_clear_unrel },
@@ -351,30 +339,21 @@ const struct bt_mesh_model_op bt_mesh_health_op[] = {
 	BT_MESH_MODEL_OP_END,
 };
 
-static void health_pub(struct bt_mesh_model *mod)
+static int health_pub_update(struct bt_mesh_model *mod)
 {
-	struct os_mbuf *msg = NET_BUF_SIMPLE(HEALTH_STATUS_SIZE);
+	struct bt_mesh_model_pub *pub = mod->pub;
 	size_t count;
-	int err;
 
 	BT_DBG("");
 
-	count = health_get_current(mod, msg);
+	count = health_get_current(mod, pub->msg);
 	if (!count) {
-		mod->pub->period_div = 0;
+		pub->period_div = 0;
 	}
 
-	err = bt_mesh_model_publish(mod, msg);
-	if (err) {
-		BT_ERR("Publishing failed (err %d)", err);
-	}
-	os_mbuf_free_chain(msg);
+	return 0;
 }
 
-struct bt_mesh_model_pub bt_mesh_health_pub = {
-	.func = health_pub,
-};
-
 int bt_mesh_fault_update(struct bt_mesh_elem *elem)
 {
 	struct bt_mesh_model *mod;
@@ -384,23 +363,22 @@ int bt_mesh_fault_update(struct bt_mesh_elem *elem)
 		return -EINVAL;
 	}
 
-	k_delayed_work_submit(&mod->pub->timer, K_NO_WAIT);
-	return 0;
+	return bt_mesh_model_publish(mod);
 }
 
 static void attention_off(struct os_event *work)
 {
-	struct bt_mesh_health *srv = work->ev_arg;
+	struct bt_mesh_health_srv *srv = work->ev_arg;
 	BT_DBG("");
 
-	if (srv->attention.off) {
-		srv->attention.off(srv->model);
+	if (srv->cb && srv->cb->attn_off) {
+		srv->cb->attn_off(srv->model);
 	}
 }
 
-int bt_mesh_health_init(struct bt_mesh_model *model, bool primary)
+int bt_mesh_health_srv_init(struct bt_mesh_model *model, bool primary)
 {
-	struct bt_mesh_health *srv = model->user_data;
+	struct bt_mesh_health_srv *srv = model->user_data;
 
 	if (!srv) {
 		if (!primary) {
@@ -411,8 +389,15 @@ int bt_mesh_health_init(struct bt_mesh_model *model, bool primary)
 		return -EINVAL;
 	}
 
-	k_delayed_work_init(&srv->attention.timer, attention_off);
-	k_delayed_work_add_arg(&srv->attention.timer, srv);
+	if (!model->pub) {
+		BT_ERR("Health Server has no publication support");
+		return -EINVAL;
+	}
+
+	model->pub->update = health_pub_update,
+
+	k_delayed_work_init(&srv->attn_timer, attention_off);
+	k_delayed_work_add_arg(&srv->attn_timer, srv);
 
 	srv->model = model;
 
@@ -425,7 +410,7 @@ int bt_mesh_health_init(struct bt_mesh_model *model, bool primary)
 
 void bt_mesh_attention(struct bt_mesh_model *model, u8_t time)
 {
-	struct bt_mesh_health *srv;
+	struct bt_mesh_health_srv *srv;
 
 	BT_DBG("bt_mesh_attention");
 	if (!model) {
@@ -441,16 +426,16 @@ void bt_mesh_attention(struct bt_mesh_model *model, u8_t time)
 	}
 
 	if (time) {
-		if (srv->attention.on) {
-			srv->attention.on(model);
+		if (srv->cb && srv->cb->attn_on) {
+			srv->cb->attn_on(model);
 		}
 
-		k_delayed_work_submit(&srv->attention.timer, time * 1000);
+		k_delayed_work_submit(&srv->attn_timer, time * 1000);
 	} else {
-		k_delayed_work_cancel(&srv->attention.timer);
+		k_delayed_work_cancel(&srv->attn_timer);
 
-		if (srv->attention.off) {
-			srv->attention.off(model);
+		if (srv->cb && srv->cb->attn_off) {
+			srv->cb->attn_off(model);
 		}
 	}
 }
diff --git a/net/nimble/host/mesh/src/lpn.c b/net/nimble/host/mesh/src/lpn.c
index 2cfd9eb0a..165bc23b9 100644
--- a/net/nimble/host/mesh/src/lpn.c
+++ b/net/nimble/host/mesh/src/lpn.c
@@ -23,25 +23,36 @@
 #include "transport.h"
 #include "access.h"
 #include "beacon.h"
+#include "foundation.h"
 #include "lpn.h"
 
+#if MYNEWT_VAL(BLE_MESH_LPN_AUTO)
+#define LPN_AUTO_TIMEOUT          K_SECONDS(MYNEWT_VAL(BLE_MESH_LPN_AUTO_TIMEOUT))
+#else
+#define LPN_AUTO_TIMEOUT          0
+#endif
+
 #define LPN_RECV_DELAY            MYNEWT_VAL(BLE_MESH_LPN_RECV_DELAY)
 #define SCAN_LATENCY              min(MYNEWT_VAL(BLE_MESH_LPN_SCAN_LATENCY), \
 				      LPN_RECV_DELAY)
 
-#define FRIEND_REQ_RETRY_TIMEOUT  K_SECONDS(5)
-#define FRIEND_REQ_TIMEOUT        (K_MSEC(100) + K_SECONDS(1))
+#define FRIEND_REQ_RETRY_TIMEOUT  K_SECONDS(MYNEWT_VAL(BLE_MESH_LPN_RETRY_TIMEOUT))
+
+#define FRIEND_REQ_WAIT           K_MSEC(100)
+#define FRIEND_REQ_SCAN           K_SECONDS(1)
+#define FRIEND_REQ_TIMEOUT        (FRIEND_REQ_WAIT + FRIEND_REQ_SCAN)
 
 #define POLL_RETRY_TIMEOUT        K_MSEC(100)
 
-#define REQ_ATTEMPTS(lpn) ((lpn)->poll_timeout < K_SECONDS(3) ? 2 : 4)
+#define REQ_RETRY_DURATION(lpn)  (4 * (LPN_RECV_DELAY + (lpn)->adv_duration + \
+				       (lpn)->recv_win + POLL_RETRY_TIMEOUT))
 
-#define REQ_RETRY_DURATION(lpn)  (REQ_ATTEMPTS(lpn) * \
-				  (LPN_RECV_DELAY + (lpn)->recv_win + \
-				   POLL_RETRY_TIMEOUT))
+#define POLL_TIMEOUT_INIT     (MYNEWT_VAL(BLE_MESH_LPN_INIT_POLL_TIMEOUT) * 100)
+#define POLL_TIMEOUT_MAX(lpn)   ((MYNEWT_VAL(BLE_MESH_LPN_POLL_TIMEOUT) * 100) - \
+				 REQ_RETRY_DURATION(lpn))
+#define REQ_ATTEMPTS(lpn)     (POLL_TIMEOUT_MAX(lpn) < K_SECONDS(3) ? 2 : 4)
 
-#define POLL_TIMEOUT(lpn)   ((MYNEWT_VAL(BLE_MESH_LPN_POLL_TIMEOUT) * 100) - \
-			     REQ_RETRY_DURATION(lpn))
+#define CLEAR_ATTEMPTS        2
 
 #define LPN_CRITERIA ((MYNEWT_VAL(BLE_MESH_LPN_MIN_QUEUE_SIZE)) | \
 		      (MYNEWT_VAL(BLE_MESH_LPN_RSSI_FACTOR) << 3) | \
@@ -50,6 +61,11 @@
 #define POLL_TO(to) { (u8_t)((to) >> 16), (u8_t)((to) >> 8), (u8_t)(to) }
 #define LPN_POLL_TO POLL_TO(MYNEWT_VAL(BLE_MESH_LPN_POLL_TIMEOUT))
 
+/* 2 transmissions, 20ms interval */
+#define POLL_XMIT BT_MESH_TRANSMIT(1, 20)
+
+static void (*lpn_cb)(u16_t friend_addr, bool established);
+
 #if (MYNEWT_VAL(BLE_MESH_DEBUG_LOW_POWER))
 static const char *state2str(int state)
 {
@@ -58,12 +74,14 @@ static const char *state2str(int state)
 		return "disabled";
 	case BT_MESH_LPN_CLEAR:
 		return "clear";
+	case BT_MESH_LPN_TIMER:
+		return "timer";
 	case BT_MESH_LPN_ENABLED:
 		return "enabled";
+	case BT_MESH_LPN_REQ_WAIT:
+		return "req wait";
 	case BT_MESH_LPN_WAIT_OFFER:
 		return "wait offer";
-	case BT_MESH_LPN_ESTABLISHING:
-		return "establishing";
 	case BT_MESH_LPN_ESTABLISHED:
 		return "established";
 	case BT_MESH_LPN_RECV_DELAY:
@@ -84,26 +102,73 @@ static inline void lpn_set_state(int state)
 	bt_mesh.lpn.state = state;
 }
 
-static void clear_friendship(bool disable);
+static inline void group_zero(atomic_t *target)
+{
+#if CONFIG_BT_MESH_LPN_GROUPS > 32
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) {
+		atomic_set(&target[i], 0);
+	}
+#else
+	atomic_set(target, 0);
+#endif
+}
+
+static inline void group_set(atomic_t *target, atomic_t *source)
+{
+#if CONFIG_BT_MESH_LPN_GROUPS > 32
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) {
+		atomic_or(&target[i], atomic_get(&source[i]));
+	}
+#else
+	atomic_or(target, atomic_get(source));
+#endif
+}
+
+static inline void group_clear(atomic_t *target, atomic_t *source)
+{
+#if CONFIG_BT_MESH_LPN_GROUPS > 32
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) {
+		atomic_and(&target[i], ~atomic_get(&source[i]));
+	}
+#else
+	atomic_and(target, ~atomic_get(source));
+#endif
+}
+
+static void clear_friendship(bool force, bool disable);
 
-static void friend_clear_sent(struct os_mbuf *buf, int err)
+static void friend_clear_sent(int err, void *user_data)
 {
+	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
+
 	/* We're switching away from Low Power behavior, so permanently
 	 * enable scanning.
 	 */
 	bt_mesh_scan_enable();
 
+	lpn->req_attempts++;
+
 	if (err) {
 		BT_ERR("Sending Friend Request failed (err %d)", err);
 		lpn_set_state(BT_MESH_LPN_ENABLED);
-		clear_friendship(bt_mesh.lpn.disable);
+		clear_friendship(false, lpn->disable);
 		return;
 	}
 
 	lpn_set_state(BT_MESH_LPN_CLEAR);
-	k_delayed_work_submit(&bt_mesh.lpn.timer, FRIEND_REQ_TIMEOUT);
+	k_delayed_work_submit(&lpn->timer, FRIEND_REQ_TIMEOUT);
 }
 
+static const struct bt_mesh_send_cb clear_sent_cb = {
+	.end = friend_clear_sent,
+};
+
 static int send_friend_clear(void)
 {
 	struct bt_mesh_msg_ctx ctx = {
@@ -116,6 +181,7 @@ static int send_friend_clear(void)
 		.sub = &bt_mesh.sub[0],
 		.ctx = &ctx,
 		.src = bt_mesh_primary_addr(),
+		.xmit = bt_mesh_net_transmit_get(),
 	};
 	struct bt_mesh_ctl_friend_clear req = {
 		.lpn_addr    = sys_cpu_to_be16(tx.src),
@@ -125,14 +191,17 @@ static int send_friend_clear(void)
 	BT_DBG("");
 
 	return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR, &req,
-				sizeof(req), friend_clear_sent);
+				sizeof(req), NULL, &clear_sent_cb, NULL);
 }
 
-static void clear_friendship(bool disable)
+static void clear_friendship(bool force, bool disable)
 {
 	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
 
-	if (lpn->state >= BT_MESH_LPN_ESTABLISHED) {
+	BT_DBG("force %u disable %u", force, disable);
+
+	if (!force && lpn->established && !lpn->clear_success &&
+	    lpn->req_attempts < CLEAR_ATTEMPTS) {
 		send_friend_clear();
 		lpn->disable = disable;
 		return;
@@ -142,7 +211,17 @@ static void clear_friendship(bool disable)
 
 	k_delayed_work_cancel(&lpn->timer);
 
-	bt_mesh_friend_cred_del(bt_mesh.sub[0].net_idx, lpn->frnd);
+	friend_cred_del(bt_mesh.sub[0].net_idx, lpn->frnd);
+
+	if (lpn->clear_success) {
+		lpn->old_friend = BT_MESH_ADDR_UNASSIGNED;
+	} else {
+		lpn->old_friend = lpn->frnd;
+	}
+
+	if (lpn_cb && lpn->frnd != BT_MESH_ADDR_UNASSIGNED) {
+		lpn_cb(lpn->frnd, false);
+	}
 
 	lpn->frnd = BT_MESH_ADDR_UNASSIGNED;
 	lpn->fsn = 0;
@@ -151,6 +230,12 @@ static void clear_friendship(bool disable)
 	lpn->queue_size = 0;
 	lpn->disable = 0;
 	lpn->sent_req = 0;
+	lpn->established = 0;
+	lpn->clear_success = 0;
+
+	group_zero(lpn->added);
+	group_zero(lpn->pending);
+	group_zero(lpn->to_remove);
 
 	/* Set this to 1 to force group subscription when the next
 	 * Friendship is created, in case lpn->groups doesn't get
@@ -167,18 +252,32 @@ static void clear_friendship(bool disable)
 	k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT);
 }
 
-static void friend_req_sent(struct os_mbuf *buf, int err)
+static void friend_req_sent(u16_t duration, int err, void *user_data)
 {
+	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
+
 	if (err) {
 		BT_ERR("Sending Friend Request failed (err %d)", err);
 		return;
 	}
 
-	lpn_set_state(BT_MESH_LPN_WAIT_OFFER);
-	k_delayed_work_submit(&bt_mesh.lpn.timer, FRIEND_REQ_TIMEOUT);
+	lpn->adv_duration = duration;
+
+	if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
+		k_delayed_work_submit(&lpn->timer, FRIEND_REQ_WAIT);
+		lpn_set_state(BT_MESH_LPN_REQ_WAIT);
+	} else {
+		k_delayed_work_submit(&lpn->timer,
+				      duration + FRIEND_REQ_TIMEOUT);
+		lpn_set_state(BT_MESH_LPN_WAIT_OFFER);
+	}
 }
 
-static int send_friend_req(void)
+static const struct bt_mesh_send_cb friend_req_sent_cb = {
+	.start = friend_req_sent,
+};
+
+static int send_friend_req(struct bt_mesh_lpn *lpn)
 {
 	const struct bt_mesh_comp *comp = bt_mesh_comp_get();
 	struct bt_mesh_msg_ctx ctx = {
@@ -191,64 +290,31 @@ static int send_friend_req(void)
 		.sub = &bt_mesh.sub[0],
 		.ctx = &ctx,
 		.src = bt_mesh_primary_addr(),
+		.xmit = POLL_XMIT,
 	};
 	struct bt_mesh_ctl_friend_req req = {
 		.criteria    = LPN_CRITERIA,
 		.recv_delay  = LPN_RECV_DELAY,
 		.poll_to     = LPN_POLL_TO,
-		.prev_addr   = BT_MESH_ADDR_UNASSIGNED,
+		.prev_addr   = lpn->old_friend,
 		.num_elem    = comp->elem_count,
-		.lpn_counter = sys_cpu_to_be16(bt_mesh.lpn.counter),
+		.lpn_counter = sys_cpu_to_be16(lpn->counter),
 	};
 
 	BT_DBG("");
 
 	return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_REQ, &req,
-				sizeof(req), friend_req_sent);
+				sizeof(req), NULL, &friend_req_sent_cb, NULL);
 }
 
-static inline void group_zero(atomic_t *target)
+static void req_sent(u16_t duration, int err, void *user_data)
 {
-#if CONFIG_BLUETOOTH_MESH_LPN_GROUPS > 32
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) {
-		atomic_set(&target[i], 0);
-	}
-#else
-	atomic_set(target, 0);
-#endif
-}
-
-static inline void group_set(atomic_t *target, atomic_t *source)
-{
-#if CONFIG_BLUETOOTH_MESH_LPN_GROUPS > 32
-	int i;
-
-	for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) {
-		atomic_or(&target[i], atomic_get(&source[i]));
-	}
-#else
-	atomic_or(target, atomic_get(source));
-#endif
-}
-
-static inline void group_clear(atomic_t *target, atomic_t *source)
-{
-#if CONFIG_BLUETOOTH_MESH_LPN_GROUPS > 32
-	int i;
+	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
 
-	for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) {
-		atomic_and(&target[i], ~atomic_get(&source[i]));
-	}
-#else
-	atomic_and(target, ~atomic_get(source));
+#if BT_DBG_ENABLED
+	BT_DBG("req 0x%02x duration %u err %d state %s",
+	       lpn->sent_req, duration, err, state2str(lpn->state));
 #endif
-}
-
-static void req_sent(struct os_mbuf *buf, int err)
-{
-	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
 
 	if (err) {
 		BT_ERR("Sending request failed (err %d)", err);
@@ -258,20 +324,26 @@ static void req_sent(struct os_mbuf *buf, int err)
 	}
 
 	lpn->req_attempts++;
+	lpn->adv_duration = duration;
 
-	if (lpn->state == BT_MESH_LPN_ESTABLISHING) {
-		k_delayed_work_submit(&lpn->timer,
-				      LPN_RECV_DELAY + lpn->recv_win);
-	} else { /* Established Friendship */
+	if (lpn->established || IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
 		lpn_set_state(BT_MESH_LPN_RECV_DELAY);
 		/* We start scanning a bit early to elimitate risk of missing
 		 * response data due to HCI and other latencies.
 		 */
 		k_delayed_work_submit(&lpn->timer,
 				      LPN_RECV_DELAY - SCAN_LATENCY);
+	} else {
+		k_delayed_work_submit(&lpn->timer,
+				      LPN_RECV_DELAY + duration +
+				      lpn->recv_win);
 	}
 }
 
+static const struct bt_mesh_send_cb req_sent_cb = {
+	.start = req_sent,
+};
+
 static int send_friend_poll(void)
 {
 	struct bt_mesh_msg_ctx ctx = {
@@ -279,12 +351,13 @@ static int send_friend_poll(void)
 		.app_idx     = BT_MESH_KEY_UNUSED,
 		.addr        = bt_mesh.lpn.frnd,
 		.send_ttl    = 0,
-		.friend_cred = 1,
 	};
 	struct bt_mesh_net_tx tx = {
 		.sub = &bt_mesh.sub[0],
 		.ctx = &ctx,
 		.src = bt_mesh_primary_addr(),
+		.xmit = POLL_XMIT,
+		.friend_cred = true,
 	};
 	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
 	u8_t fsn = lpn->fsn;
@@ -301,7 +374,7 @@ static int send_friend_poll(void)
 	}
 
 	err = bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_POLL, &fsn, 1,
-			       req_sent);
+			       NULL, &req_sent_cb, NULL);
 	if (err == 0) {
 		lpn->pending_poll = 0;
 		lpn->sent_req = TRANS_CTL_OP_FRIEND_POLL;
@@ -310,23 +383,25 @@ static int send_friend_poll(void)
 	return err;
 }
 
-void bt_mesh_lpn_disable(void)
+void bt_mesh_lpn_disable(bool force)
 {
 	if (bt_mesh.lpn.state == BT_MESH_LPN_DISABLED) {
 		return;
 	}
 
-	clear_friendship(true);
+	clear_friendship(force, true);
 }
 
 int bt_mesh_lpn_set(bool enable)
 {
+	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
+
 	if (enable) {
-		if (bt_mesh.lpn.state != BT_MESH_LPN_DISABLED) {
+		if (lpn->state != BT_MESH_LPN_DISABLED) {
 			return 0;
 		}
 	} else {
-		if (bt_mesh.lpn.state == BT_MESH_LPN_DISABLED) {
+		if (lpn->state == BT_MESH_LPN_DISABLED) {
 			return 0;
 		}
 	}
@@ -342,9 +417,21 @@ int bt_mesh_lpn_set(bool enable)
 	}
 
 	if (enable) {
-		send_friend_req();
+		lpn_set_state(BT_MESH_LPN_ENABLED);
+
+		if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
+			bt_mesh_scan_disable();
+		}
+
+		send_friend_req(lpn);
 	} else {
-		bt_mesh_lpn_disable();
+		if (IS_ENABLED(CONFIG_BT_MESH_LPN_AUTO) &&
+		    lpn->state == BT_MESH_LPN_TIMER) {
+			k_delayed_work_cancel(&lpn->timer);
+			lpn_set_state(BT_MESH_LPN_DISABLED);
+		} else {
+			bt_mesh_lpn_disable(false);
+		}
 	}
 
 	return 0;
@@ -369,6 +456,12 @@ void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx)
 {
 	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
 
+	if (lpn->state == BT_MESH_LPN_TIMER) {
+		BT_DBG("Restarting establishment timer");
+		k_delayed_work_submit(&lpn->timer, LPN_AUTO_TIMEOUT);
+		return;
+	}
+
 	if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) {
 		BT_WARN("Unexpected message withouth a preceding Poll");
 		return;
@@ -387,7 +480,7 @@ int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx,
 	struct bt_mesh_ctl_friend_offer *msg = (void *)buf->om_data;
 	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
 	struct bt_mesh_subnet *sub = rx->sub;
-	struct bt_mesh_friend_cred *cred;
+	struct friend_cred *cred;
 	u16_t frnd_counter;
 	int err;
 
@@ -401,6 +494,11 @@ int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx,
 		return 0;
 	}
 
+	if (!msg->recv_win) {
+		BT_WARN("Prohibited ReceiveWindow value");
+		return -EINVAL;
+	}
+
 	frnd_counter = sys_be16_to_cpu(msg->frnd_counter);
 
 	BT_DBG("recv_win %u queue_size %u sub_list_size %u rssi %d counter %u",
@@ -409,22 +507,12 @@ int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx,
 
 	lpn->frnd = rx->ctx.addr;
 
-	cred = bt_mesh_friend_cred_add(sub->net_idx, sub->keys[0].net, 0,
-				       lpn->frnd, lpn->counter, frnd_counter);
+	cred = friend_cred_create(sub, lpn->frnd, lpn->counter, frnd_counter);
 	if (!cred) {
 		lpn->frnd = BT_MESH_ADDR_UNASSIGNED;
 		return -ENOMEM;
 	}
 
-	if (sub->kr_flag) {
-		err = bt_mesh_friend_cred_set(cred, 1, sub->keys[1].net);
-		if (err) {
-			bt_mesh_friend_cred_clear(cred);
-			lpn->frnd = BT_MESH_ADDR_UNASSIGNED;
-			return err;
-		}
-	}
-
 	/* TODO: Add offer acceptance criteria check */
 
 	k_delayed_work_cancel(&lpn->timer);
@@ -434,7 +522,7 @@ int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx,
 
 	err = send_friend_poll();
 	if (err) {
-		bt_mesh_friend_cred_clear(cred);
+		friend_cred_clear(cred);
 		lpn->frnd = BT_MESH_ADDR_UNASSIGNED;
 		lpn->recv_win = 0;
 		lpn->queue_size = 0;
@@ -442,7 +530,6 @@ int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx,
 	}
 
 	lpn->counter++;
-	lpn_set_state(BT_MESH_LPN_ESTABLISHING);
 
 	return 0;
 }
@@ -474,7 +561,8 @@ int bt_mesh_lpn_friend_clear_cfm(struct bt_mesh_net_rx *rx,
 		return 0;
 	}
 
-	clear_friendship(bt_mesh.lpn.disable);
+	lpn->clear_success = 1;
+	clear_friendship(false, lpn->disable);
 
 	return 0;
 }
@@ -545,16 +633,19 @@ static bool sub_update(u8_t op)
 		.app_idx     = BT_MESH_KEY_UNUSED,
 		.addr        = lpn->frnd,
 		.send_ttl    = 0,
-		.friend_cred = 1,
 	};
 	struct bt_mesh_net_tx tx = {
 		.sub = &bt_mesh.sub[0],
 		.ctx = &ctx,
 		.src = bt_mesh_primary_addr(),
+		.xmit = POLL_XMIT,
+		.friend_cred = true,
 	};
 	struct bt_mesh_ctl_friend_sub req;
 	size_t i, g;
 
+	BT_DBG("op 0x%02x sent_req 0x%02x", op, lpn->sent_req);
+
 	if (lpn->sent_req) {
 		return false;
 	}
@@ -594,7 +685,8 @@ static bool sub_update(u8_t op)
 
 	req.xact = lpn->xact_next++;
 
-	if (bt_mesh_ctl_send(&tx, op, &req, 1 + g * 2, req_sent) < 0) {
+	if (bt_mesh_ctl_send(&tx, op, &req, 1 + g * 2, NULL,
+			     &req_sent_cb, NULL) < 0) {
 		group_zero(lpn->pending);
 		return false;
 	}
@@ -604,6 +696,31 @@ static bool sub_update(u8_t op)
 	return true;
 }
 
+static void update_timeout(struct bt_mesh_lpn *lpn)
+{
+	if (lpn->established) {
+		BT_WARN("No response from Friend during ReceiveWindow");
+		bt_mesh_scan_disable();
+		lpn_set_state(BT_MESH_LPN_ESTABLISHED);
+		k_delayed_work_submit(&lpn->timer, POLL_RETRY_TIMEOUT);
+	} else {
+		if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
+			bt_mesh_scan_disable();
+		}
+
+		if (lpn->req_attempts < 6) {
+			BT_WARN("Retrying first Friend Poll");
+			lpn->sent_req = 0;
+			if (send_friend_poll() == 0) {
+				return;
+			}
+		}
+
+		BT_ERR("Timed out waiting for first Friend Update");
+		clear_friendship(false, false);
+	}
+}
+
 static void lpn_timeout(struct os_event *work)
 {
 	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
@@ -616,21 +733,33 @@ static void lpn_timeout(struct os_event *work)
 	case BT_MESH_LPN_DISABLED:
 		break;
 	case BT_MESH_LPN_CLEAR:
-		clear_friendship(bt_mesh.lpn.disable);
+		clear_friendship(false, bt_mesh.lpn.disable);
 		break;
+	case BT_MESH_LPN_TIMER:
+		BT_DBG("Starting to look for Friend nodes");
+		lpn_set_state(BT_MESH_LPN_ENABLED);
+		if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
+			bt_mesh_scan_disable();
+		}
+		/* fall through */
 	case BT_MESH_LPN_ENABLED:
-		send_friend_req();
+		send_friend_req(lpn);
+		break;
+	case BT_MESH_LPN_REQ_WAIT:
+		bt_mesh_scan_enable();
+		k_delayed_work_submit(&lpn->timer,
+				      lpn->adv_duration + FRIEND_REQ_SCAN);
+		lpn_set_state(BT_MESH_LPN_WAIT_OFFER);
 		break;
 	case BT_MESH_LPN_WAIT_OFFER:
 		BT_WARN("No acceptable Friend Offers received");
+		if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
+			bt_mesh_scan_disable();
+		}
 		lpn->counter++;
 		lpn_set_state(BT_MESH_LPN_ENABLED);
 		k_delayed_work_submit(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT);
 		break;
-	case BT_MESH_LPN_ESTABLISHING:
-		BT_ERR("Timed out waiting for first Friend Update");
-		clear_friendship(false);
-		break;
 	case BT_MESH_LPN_ESTABLISHED:
 		if (lpn->req_attempts < REQ_ATTEMPTS(lpn)) {
 			u8_t req = lpn->sent_req;
@@ -648,19 +777,18 @@ static void lpn_timeout(struct os_event *work)
 
 		BT_ERR("No response from Friend after %u retries",
 		       lpn->req_attempts);
-		clear_friendship(false);
+		lpn->req_attempts = 0;
+		clear_friendship(false, false);
 		break;
 	case BT_MESH_LPN_RECV_DELAY:
+		k_delayed_work_submit(&lpn->timer,
+				      lpn->adv_duration + SCAN_LATENCY +
+				      lpn->recv_win);
 		bt_mesh_scan_enable();
 		lpn_set_state(BT_MESH_LPN_WAIT_UPDATE);
-		k_delayed_work_submit(&lpn->timer,
-				      lpn->recv_win + SCAN_LATENCY);
 		break;
 	case BT_MESH_LPN_WAIT_UPDATE:
-		BT_WARN("No response from Friend during ReceiveWindow");
-		bt_mesh_scan_disable();
-		lpn_set_state(BT_MESH_LPN_ESTABLISHED);
-		k_delayed_work_submit(&lpn->timer, POLL_RETRY_TIMEOUT);
+		update_timeout(lpn);
 		break;
 	default:
 		__ASSERT(0, "Unhandled LPN state");
@@ -703,12 +831,13 @@ static s32_t poll_timeout(struct bt_mesh_lpn *lpn)
 {
 	/* If we're waiting for segment acks keep polling at high freq */
 	if (bt_mesh_tx_in_progress()) {
-		return min(POLL_TIMEOUT(lpn), K_SECONDS(1));
+		return min(POLL_TIMEOUT_MAX(lpn), K_SECONDS(1));
 	}
 
-	if (lpn->poll_timeout < POLL_TIMEOUT(lpn)) {
+	if (lpn->poll_timeout < POLL_TIMEOUT_MAX(lpn)) {
 		lpn->poll_timeout *= 2;
-		lpn->poll_timeout = min(lpn->poll_timeout, POLL_TIMEOUT(lpn));
+		lpn->poll_timeout = min(lpn->poll_timeout,
+					POLL_TIMEOUT_MAX(lpn));
 	}
 
 	BT_DBG("Poll Timeout is %ums", lpn->poll_timeout);
@@ -809,21 +938,28 @@ int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx,
 		bt_mesh_beacon_ivu_initiator(false);
 	}
 
-	if (lpn->state == BT_MESH_LPN_ESTABLISHING) {
+	if (!lpn->established) {
 		/* This is normally checked on the transport layer, however
 		 * in this state we're also still accepting master
 		 * credentials so we need to ensure the right ones (Friend
 		 * Credentials) were used for this message.
 		 */
-		if (!rx->ctx.friend_cred) {
+		if (!rx->friend_cred) {
 			BT_WARN("Friend Update with wrong credentials");
 			return -EINVAL;
 		}
 
+		lpn->established = 1;
+
 		BT_INFO("Friendship established with 0x%04x", lpn->frnd);
 
+		if (lpn_cb) {
+			lpn_cb(lpn->frnd, true);
+		}
+
 		/* Set initial poll timeout */
-		lpn->poll_timeout = min(POLL_TIMEOUT(lpn), K_SECONDS(1));
+		lpn->poll_timeout = min(POLL_TIMEOUT_MAX(lpn),
+					POLL_TIMEOUT_INIT);
 	}
 
 	friend_response_received(lpn);
@@ -838,7 +974,7 @@ int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx,
 		bt_mesh_net_beacon_update(sub);
 	}
 
-	bt_mesh_iv_update(iv_index, BT_MESH_IV_UPDATE(msg->flags));
+	bt_mesh_net_iv_update(iv_index, BT_MESH_IV_UPDATE(msg->flags));
 
 	if (lpn->groups_changed) {
 		sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD);
@@ -861,20 +997,46 @@ int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx,
 	return 0;
 }
 
-void bt_mesh_lpn_friend_poll(void)
+int bt_mesh_lpn_poll(void)
 {
+	if (!bt_mesh.lpn.established) {
+		return -EAGAIN;
+	}
+
 	BT_DBG("Requesting more messages");
-	send_friend_poll();
+
+	return send_friend_poll();
+}
+
+void bt_mesh_lpn_set_cb(void (*cb)(u16_t friend_addr, bool established))
+{
+	lpn_cb = cb;
 }
 
 int bt_mesh_lpn_init(void)
 {
 	struct bt_mesh_lpn *lpn = &bt_mesh.lpn;
 
+	BT_DBG("");
+
 	k_delayed_work_init(&lpn->timer, lpn_timeout);
 
 	if (lpn->state == BT_MESH_LPN_ENABLED) {
-		send_friend_req();
+		if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) {
+			bt_mesh_scan_disable();
+		} else {
+			bt_mesh_scan_enable();
+		}
+
+		send_friend_req(lpn);
+	} else {
+		bt_mesh_scan_enable();
+
+		if (IS_ENABLED(CONFIG_BT_MESH_LPN_AUTO)) {
+			BT_DBG("Waiting %u ms for messages", LPN_AUTO_TIMEOUT);
+			lpn_set_state(BT_MESH_LPN_TIMER);
+			k_delayed_work_submit(&lpn->timer, LPN_AUTO_TIMEOUT);
+		}
 	}
 
 	return 0;
diff --git a/net/nimble/host/mesh/src/lpn.h b/net/nimble/host/mesh/src/lpn.h
index b1df8b315..0ff6c9cfd 100644
--- a/net/nimble/host/mesh/src/lpn.h
+++ b/net/nimble/host/mesh/src/lpn.h
@@ -10,50 +10,59 @@
 
 #include "mesh/mesh.h"
 
-int
-bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
-int
-bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
-int
-bt_mesh_lpn_friend_clear_cfm(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
-int
-bt_mesh_lpn_friend_sub_cfm(struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
-
-static inline bool
-bt_mesh_lpn_established(void)
+int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx,
+			      struct os_mbuf *buf);
+int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx,
+			     struct os_mbuf *buf);
+int bt_mesh_lpn_friend_clear_cfm(struct bt_mesh_net_rx *rx,
+				 struct os_mbuf *buf);
+int bt_mesh_lpn_friend_sub_cfm(struct bt_mesh_net_rx *rx,
+			       struct os_mbuf *buf);
+
+static inline bool bt_mesh_lpn_established(void)
 {
 #if (MYNEWT_VAL(BLE_MESH_LOW_POWER))
-    return (bt_mesh.lpn.state >= BT_MESH_LPN_ESTABLISHED);
+	return bt_mesh.lpn.established;
 #else
-    return false;
+	return false;
+#endif
+}
+
+static inline bool bt_mesh_lpn_match(u16_t addr)
+{
+#if (MYNEWT_VAL(BLE_MESH_LOW_POWER))
+	if (bt_mesh_lpn_established()) {
+		return (addr == bt_mesh.lpn.frnd);
+	}
 #endif
+	return false;
 }
 
-static inline bool
-bt_mesh_lpn_waiting_update(void)
+static inline bool bt_mesh_lpn_waiting_update(void)
 {
 #if (MYNEWT_VAL(BLE_MESH_LOW_POWER))
-    return (bt_mesh.lpn.state == BT_MESH_LPN_WAIT_UPDATE);
+	return (bt_mesh.lpn.state == BT_MESH_LPN_WAIT_UPDATE);
 #else
-    return false;
+	return false;
 #endif
 }
 
-void
-bt_mesh_lpn_friend_poll(void);
+static inline bool bt_mesh_lpn_timer(void)
+{
+#if MYNEWT_VAL(BLE_MESH_LOW_POWER) && MYNEWT_VAL(BLE_MESH_LPN_AUTO)
+	return (bt_mesh.lpn.state == BT_MESH_LPN_TIMER);
+#else
+	return false;
+#endif
+}
 
-void
-bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx);
+void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx);
 
-void
-bt_mesh_lpn_group_add(u16_t group);
-void
-bt_mesh_lpn_group_del(u16_t *groups, size_t group_count);
+void bt_mesh_lpn_group_add(u16_t group);
+void bt_mesh_lpn_group_del(u16_t *groups, size_t group_count);
 
-void
-bt_mesh_lpn_disable(void);
+void bt_mesh_lpn_disable(bool force);
 
-int
-bt_mesh_lpn_init(void);
+int bt_mesh_lpn_init(void);
 
 #endif
diff --git a/net/nimble/host/mesh/src/mesh.c b/net/nimble/host/mesh/src/mesh.c
index 73ef2bb3f..04712f78e 100644
--- a/net/nimble/host/mesh/src/mesh.c
+++ b/net/nimble/host/mesh/src/mesh.c
@@ -26,187 +26,235 @@
 #include "access.h"
 #include "foundation.h"
 #include "proxy.h"
+#include "shell.h"
 #include "mesh_priv.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])
+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])
 {
-    int err;
+	int err;
 
-    BLE_HS_LOG(INFO, "Primary Element: 0x%04x", addr);
+	BT_INFO("Primary Element: 0x%04x", addr);
+	BT_DBG("net_idx 0x%04x flags 0x%02x iv_index 0x%04x",
+	       net_idx, flags, iv_index);
 
-    if ((MYNEWT_VAL(BLE_MESH_PB_GATT))) {
-        bt_mesh_proxy_prov_disable();
-    }
+	if ((MYNEWT_VAL(BLE_MESH_PB_GATT))) {
+		bt_mesh_proxy_prov_disable();
+	}
 
-    err = bt_mesh_net_create(net_idx, flags, net_key, iv_index);
-    if (err) {
-        if ((MYNEWT_VAL(BLE_MESH_PB_GATT))) {
-            bt_mesh_proxy_prov_enable();
-        }
+	err = bt_mesh_net_create(net_idx, flags, net_key, iv_index);
+	if (err) {
+		if ((MYNEWT_VAL(BLE_MESH_PB_GATT))) {
+			bt_mesh_proxy_prov_enable();
+		}
 
-        return err;
-    }
+		return err;
+	}
 
-    bt_mesh.seq = seq;
+	bt_mesh.seq = seq;
 
-    bt_mesh_comp_provision(addr);
+	bt_mesh_comp_provision(addr);
 
-    memcpy(bt_mesh.dev_key, dev_key, 16);
+	memcpy(bt_mesh.dev_key, dev_key, 16);
 
-    provisioned = true;
+	provisioned = true;
 
-    if (bt_mesh_beacon_get() == BT_MESH_BEACON_ENABLED) {
-        bt_mesh_beacon_enable();
-    }
-    else {
-        bt_mesh_beacon_disable();
-    }
+	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 (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 PB-ADV is disabled then scanning will have been disabled */
-    if (!(MYNEWT_VAL(BLE_MESH_PB_ADV))) {
-        bt_mesh_scan_enable();
-    }
+	if ((MYNEWT_VAL(BLE_MESH_LOW_POWER))) {
+		bt_mesh_lpn_init();
+	} else {
+		bt_mesh_scan_enable();
+	}
 
-    if ((MYNEWT_VAL(BLE_MESH_LOW_POWER))) {
-        bt_mesh_lpn_init();
-    }
+	if ((MYNEWT_VAL(BLE_MESH_FRIEND))) {
+		bt_mesh_friend_init();
+	}
 
-    if ((MYNEWT_VAL(BLE_MESH_FRIEND))) {
-        bt_mesh_friend_init();
-    }
+	if (MYNEWT_VAL(BLE_MESH_PROV)) {
+		bt_mesh_prov_complete(net_idx, addr);
+	}
 
-    return 0;
+	return 0;
 }
 
-void
-bt_mesh_reset(void)
+void bt_mesh_reset(void)
 {
-    if (!provisioned) {
-        goto enable_beacon;
-    }
-
-    bt_mesh_comp_unprovision();
-
-    bt_mesh.iv_index = 0;
-    bt_mesh.seq = 0;
-    bt_mesh.iv_update = 0;
-    bt_mesh.valid = 0;
-    bt_mesh.last_update = 0;
-    bt_mesh.ivu_initiator = 0;
-
-    k_delayed_work_cancel(&bt_mesh.ivu_complete);
-
-    if ((MYNEWT_VAL(BLE_MESH_LOW_POWER))) {
-        bt_mesh_lpn_disable();
-    }
-
-    if ((MYNEWT_VAL(BLE_MESH_GATT_PROXY))) {
-        bt_mesh_proxy_gatt_disable();
-    }
-
-    if ((MYNEWT_VAL(BLE_MESH_PB_GATT))) {
-        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));
-
-    provisioned = false;
-
-enable_beacon:
-    if ((MYNEWT_VAL(BLE_MESH_PB_ADV))) {
-        /* Make sure we're scanning for provisioning inviations */
-        bt_mesh_scan_enable();
-        /* Enable unprovisioned beacon sending */
-        bt_mesh_beacon_enable();
-    }
-    else {
-        bt_mesh_scan_disable();
-        bt_mesh_beacon_disable();
-    }
+	if (!provisioned) {
+		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_initiator = 0;
+
+	k_delayed_work_cancel(&bt_mesh.ivu_complete);
+
+	bt_mesh_cfg_reset();
+
+	bt_mesh_rx_reset();
+	bt_mesh_tx_reset();
+
+	if ((MYNEWT_VAL(BLE_MESH_LOW_POWER))) {
+		bt_mesh_lpn_disable(true);
+	}
+
+	if ((MYNEWT_VAL(BLE_MESH_FRIEND))) {
+		bt_mesh_friend_clear_net_idx(BT_MESH_KEY_ANY);
+	}
+
+	if ((MYNEWT_VAL(BLE_MESH_GATT_PROXY))) {
+		bt_mesh_proxy_gatt_disable();
+	}
+
+	if ((MYNEWT_VAL(BLE_MESH_PB_GATT))) {
+		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));
+
+	provisioned = false;
+
+	bt_mesh_scan_disable();
+	bt_mesh_beacon_disable();
+
+	if (IS_ENABLED(CONFIG_BT_MESH_PROV)) {
+		bt_mesh_prov_reset();
+	}
+}
+
+bool bt_mesh_is_provisioned(void)
+{
+	return provisioned;
+}
+
+int bt_mesh_prov_enable(bt_mesh_prov_bearer_t bearers)
+{
+	if (bt_mesh_is_provisioned()) {
+		return -EALREADY;
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV) &&
+	    (bearers & BT_MESH_PROV_ADV)) {
+		/* Make sure we're scanning for provisioning inviations */
+		bt_mesh_scan_enable();
+		/* Enable unprovisioned beacon sending */
+		bt_mesh_beacon_enable();
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT) &&
+	    (bearers & BT_MESH_PROV_GATT)) {
+		bt_mesh_proxy_prov_enable();
+		bt_mesh_adv_update();
+	}
+
+	return 0;
 }
 
-bool
-bt_mesh_is_provisioned(void)
+int bt_mesh_prov_disable(bt_mesh_prov_bearer_t bearers)
 {
-    return provisioned;
+	if (bt_mesh_is_provisioned()) {
+		return -EALREADY;
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV) &&
+	    (bearers & BT_MESH_PROV_ADV)) {
+		bt_mesh_beacon_disable();
+		bt_mesh_scan_disable();
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT) &&
+	    (bearers & BT_MESH_PROV_GATT)) {
+		bt_mesh_proxy_prov_disable();
+		bt_mesh_adv_update();
+	}
+
+	return 0;
 }
 
-static int
-bt_mesh_gap_event(struct ble_gap_event *event, void *arg)
+static int bt_mesh_gap_event(struct ble_gap_event *event, void *arg)
 {
-    ble_adv_gap_mesh_cb(event, arg);
+	ble_adv_gap_mesh_cb(event, arg);
 
 #if (MYNEWT_VAL(BLE_MESH_PROXY))
-    ble_mesh_proxy_gap_event(event, arg);
+	ble_mesh_proxy_gap_event(event, arg);
 #endif
 
-    return 0;
+	return 0;
 }
 
-int
-bt_mesh_init(uint8_t own_addr_type, const struct bt_mesh_prov *prov,
-             const struct bt_mesh_comp *comp)
+int bt_mesh_init(uint8_t own_addr_type, const struct bt_mesh_prov *prov,
+		 const struct bt_mesh_comp *comp)
 {
-    int err;
+	int err;
 
-    g_mesh_addr_type = own_addr_type;
+	g_mesh_addr_type = own_addr_type;
 
-    /* initialize SM alg ECC subsystem (it is used directly from mesh code) */
-    ble_sm_alg_ecc_init();
+	/* initialize SM alg ECC subsystem (it is used directly from mesh code) */
+	ble_sm_alg_ecc_init();
 
-    err = bt_mesh_comp_register(comp);
-    if (err) {
-        return err;
-    }
+	err = bt_mesh_comp_register(comp);
+	if (err) {
+		return err;
+	}
 
-    if (MYNEWT_VAL(BLE_MESH_PROV)) {
-        err = bt_mesh_prov_init(prov);
-        if (err) {
-            return err;
-        }
-
-    }
+	if (MYNEWT_VAL(BLE_MESH_PROV)) {
+		err = bt_mesh_prov_init(prov);
+		if (err) {
+			return err;
+		}
+	}
 
 #if (MYNEWT_VAL(BLE_MESH_PROXY))
-    bt_mesh_proxy_init();
-    /* Need this to proper link.rx.buf allocation */
-    bt_mesh_prov_reset_link();
+	bt_mesh_proxy_init();
+	/* Need this to proper link.rx.buf allocation */
+	bt_mesh_prov_reset_link();
 #endif
 
-    bt_mesh_net_init();
-
-    bt_mesh_trans_init();
-
-    bt_mesh_beacon_init();
-
-    bt_mesh_adv_init();
+	bt_mesh_net_init();
+	bt_mesh_trans_init();
+	bt_mesh_beacon_init();
+	bt_mesh_adv_init();
 
 #if (MYNEWT_VAL(BLE_MESH_PB_ADV))
-    /* Make sure we're scanning for provisioning inviations */
-    bt_mesh_scan_enable();
-    /* Enable unprovisioned beacon sending */
+	/* Make sure we're scanning for provisioning inviations */
+	bt_mesh_scan_enable();
+	/* Enable unprovisioned beacon sending */
+
+	bt_mesh_beacon_enable();
+#endif
 
-    bt_mesh_beacon_enable();
+#if (MYNEWT_VAL(BLE_MESH_SHELL))
+	mesh_shell_init();
 #endif
 
 #if (MYNEWT_VAL(BLE_MESH_PB_GATT))
-    bt_mesh_proxy_prov_enable();
+	bt_mesh_proxy_prov_enable();
 #endif
 
-    ble_gap_mesh_cb_register(bt_mesh_gap_event, NULL);
+	ble_gap_mesh_cb_register(bt_mesh_gap_event, NULL);
 
-    return 0;
+	return 0;
 }
diff --git a/net/nimble/host/mesh/src/mesh_priv.h b/net/nimble/host/mesh/src/mesh_priv.h
index e456d8df9..c8e869731 100644
--- a/net/nimble/host/mesh/src/mesh_priv.h
+++ b/net/nimble/host/mesh/src/mesh_priv.h
@@ -16,7 +16,6 @@
 #define BT_MESH_ADDR_IS_VIRTUAL(addr) ((addr) >= 0x8000 && (addr) < 0xc000)
 #define BT_MESH_ADDR_IS_RFU(addr) ((addr) >= 0xff00 && (addr) <= 0xfffb)
 
-bool
-bt_mesh_is_provisioned(void);
+bool bt_mesh_is_provisioned(void);
 
 #endif
diff --git a/net/nimble/host/mesh/src/net.c b/net/nimble/host/mesh/src/net.c
index 04924fdcc..a1325a7cf 100644
--- a/net/nimble/host/mesh/src/net.c
+++ b/net/nimble/host/mesh/src/net.c
@@ -29,6 +29,14 @@
 #include "foundation.h"
 #include "beacon.h"
 
+/* Minimum valid Mesh Network PDU length. The Network headers
+ * themselves take up 9 bytes. After that there is a minumum of 1 byte
+ * payload for both CTL=1 and CTL=0 PDUs (smallest OpCode is 1 byte). CTL=1
+ * PDUs must use a 64-bit (8 byte) NetMIC, whereas CTL=0 PDUs have at least
+ * a 32-bit (4 byte) NetMIC and AppMIC giving again a total of 8 bytes.
+ */
+#define BT_MESH_NET_MIN_PDU_LEN (BT_MESH_NET_HDR_LEN + 1 + 8)
+
 /* Seq limit after IV Update is triggered */
 #define IV_UPDATE_SEQ_LIMIT 8000000
 
@@ -45,11 +53,10 @@
 #define CTL(pdu)           ((pdu)[1] >> 7)
 #define TTL(pdu)           ((pdu)[1] & 0x7f)
 #define SEQ(pdu)           (((u32_t)(pdu)[2] << 16) | \
-                           ((u32_t)(pdu)[3] << 8) | (u32_t)(pdu)[4]);
+			    ((u32_t)(pdu)[3] << 8) | (u32_t)(pdu)[4]);
 #define SRC(pdu)           (sys_get_be16(&(pdu)[5]))
 #define DST(pdu)           (sys_get_be16(&(pdu)[7]))
 
-
 /* Determine how many friendship credentials we need */
 #if (MYNEWT_VAL(BLE_MESH_FRIEND))
 #define FRIEND_CRED_COUNT MYNEWT_VAL(BLE_MESH_FRIEND_LPN_COUNT)
@@ -60,7 +67,7 @@
 #endif
 
 #if FRIEND_CRED_COUNT > 0
-static struct bt_mesh_friend_cred friend_cred[FRIEND_CRED_COUNT];
+static struct friend_cred friend_cred[FRIEND_CRED_COUNT];
 #endif
 
 static u64_t msg_cache[MYNEWT_VAL(BLE_MESH_MSG_CACHE_SIZE)];
@@ -68,6 +75,7 @@ static u16_t msg_cache_next;
 
 /* Singleton network context (the implementation only supports one) */
 struct bt_mesh_net bt_mesh = {
+	.local_queue = SYS_SLIST_STATIC_INIT(bt_mesh.local_queue),
 	.sub = {
 		[0 ... (MYNEWT_VAL(BLE_MESH_SUBNET_COUNT) - 1)] = {
 			.net_idx = BT_MESH_KEY_UNUSED,
@@ -90,7 +98,6 @@ static bool check_dup(struct os_mbuf *data)
 	int i;
 
 	val = sys_get_be32(tail - 4) ^ sys_get_be32(tail - 8);
-	BT_DBG("hash=%lx", val);
 
 	for (i = 0; i < ARRAY_SIZE(dup_cache); i++) {
 		if (dup_cache[i] == val) {
@@ -204,8 +211,7 @@ int bt_mesh_net_keys_create(struct bt_mesh_subnet_keys *keys,
 
 #if ((MYNEWT_VAL(BLE_MESH_LOW_POWER)) || \
      (MYNEWT_VAL(BLE_MESH_FRIEND)))
-int bt_mesh_friend_cred_set(struct bt_mesh_friend_cred *cred, u8_t idx,
-			    const u8_t net_key[16])
+int friend_cred_set(struct friend_cred *cred, u8_t idx, const u8_t net_key[16])
 {
 	u16_t lpn_addr, frnd_addr;
 	int err;
@@ -248,12 +254,12 @@ int bt_mesh_friend_cred_set(struct bt_mesh_friend_cred *cred, u8_t idx,
 	return 0;
 }
 
-void bt_mesh_friend_cred_refresh(u16_t net_idx)
+void friend_cred_refresh(u16_t net_idx)
 {
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(friend_cred); i++) {
-		struct bt_mesh_friend_cred *cred = &friend_cred[i];
+		struct friend_cred *cred = &friend_cred[i];
 
 		if (cred->addr != BT_MESH_ADDR_UNASSIGNED &&
 		    cred->net_idx == net_idx) {
@@ -263,19 +269,21 @@ void bt_mesh_friend_cred_refresh(u16_t net_idx)
 	}
 }
 
-int bt_mesh_friend_cred_update(u16_t net_idx, u8_t idx, const u8_t net_key[16])
+int friend_cred_update(struct bt_mesh_subnet *sub)
 {
 	int err, i;
 
+	BT_DBG("net_idx 0x%04x", sub->net_idx);
+
 	for (i = 0; i < ARRAY_SIZE(friend_cred); i++) {
-		struct bt_mesh_friend_cred *cred = &friend_cred[i];
+		struct friend_cred *cred = &friend_cred[i];
 
 		if (cred->addr == BT_MESH_ADDR_UNASSIGNED ||
-		    cred->net_idx != net_idx) {
+		    cred->net_idx != sub->net_idx) {
 			continue;
 		}
 
-		err = bt_mesh_friend_cred_set(cred, idx, net_key);
+		err = friend_cred_set(cred, 1, sub->keys[1].net);
 		if (err) {
 			return err;
 		}
@@ -284,45 +292,51 @@ int bt_mesh_friend_cred_update(u16_t net_idx, u8_t idx, const u8_t net_key[16])
 	return 0;
 }
 
-struct bt_mesh_friend_cred *bt_mesh_friend_cred_add(u16_t net_idx,
-						    const u8_t net_key[16],
-						    u8_t idx, u16_t addr,
-						    u16_t lpn_counter,
-						    u16_t frnd_counter)
+struct friend_cred *friend_cred_create(struct bt_mesh_subnet *sub, u16_t addr,
+				       u16_t lpn_counter, u16_t frnd_counter)
 {
-	struct bt_mesh_friend_cred *cred;
+	struct friend_cred *cred;
 	int i, err;
 
-	BT_DBG("net_idx 0x%04x addr 0x%04x idx %u", net_idx, addr, idx);
+	BT_DBG("net_idx 0x%04x addr 0x%04x", sub->net_idx, addr);
 
 	for (cred = NULL, i = 0; i < ARRAY_SIZE(friend_cred); i++) {
 		if ((friend_cred[i].addr == BT_MESH_ADDR_UNASSIGNED) ||
 		    (friend_cred[i].addr == addr &&
-		     friend_cred[i].net_idx == net_idx)) {
+		     friend_cred[i].net_idx == sub->net_idx)) {
 			cred = &friend_cred[i];
 			break;
 		}
 	}
 
 	if (!cred) {
+		BT_WARN("No free friend credential slots");
 		return NULL;
 	}
 
-	cred->net_idx = net_idx;
+	cred->net_idx = sub->net_idx;
 	cred->addr = addr;
 	cred->lpn_counter = lpn_counter;
 	cred->frnd_counter = frnd_counter;
 
-	err = bt_mesh_friend_cred_set(cred, idx, net_key);
+	err = friend_cred_set(cred, 0, sub->keys[0].net);
 	if (err) {
-		bt_mesh_friend_cred_clear(cred);
+		friend_cred_clear(cred);
 		return NULL;
 	}
 
+	if (sub->kr_flag) {
+		err = friend_cred_set(cred, 1, sub->keys[1].net);
+		if (err) {
+			friend_cred_clear(cred);
+			return NULL;
+		}
+	}
+
 	return cred;
 }
 
-void bt_mesh_friend_cred_clear(struct bt_mesh_friend_cred *cred)
+void friend_cred_clear(struct friend_cred *cred)
 {
 	cred->net_idx = BT_MESH_KEY_UNUSED;
 	cred->addr = BT_MESH_ADDR_UNASSIGNED;
@@ -331,15 +345,15 @@ void bt_mesh_friend_cred_clear(struct bt_mesh_friend_cred *cred)
 	memset(cred->cred, 0, sizeof(cred->cred));
 }
 
-int bt_mesh_friend_cred_del(u16_t net_idx, u16_t addr)
+int friend_cred_del(u16_t net_idx, u16_t addr)
 {
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(friend_cred); i++) {
-		struct bt_mesh_friend_cred *cred = &friend_cred[i];
+		struct friend_cred *cred = &friend_cred[i];
 
 		if (cred->addr == addr && cred->net_idx == net_idx) {
-			bt_mesh_friend_cred_clear(cred);
+			friend_cred_clear(cred);
 			return 0;
 		}
 	}
@@ -347,17 +361,17 @@ int bt_mesh_friend_cred_del(u16_t net_idx, u16_t addr)
 	return -ENOENT;
 }
 
-int bt_mesh_friend_cred_get(u16_t net_idx, u16_t addr, u8_t idx,
-			    u8_t *nid, const u8_t **enc, const u8_t **priv)
+int friend_cred_get(struct bt_mesh_subnet *sub, u16_t addr, u8_t *nid,
+		    const u8_t **enc, const u8_t **priv)
 {
 	int i;
 
-	BT_DBG("net_idx 0x%04x addr 0x%04x idx %u", net_idx, addr, idx);
+	BT_DBG("net_idx 0x%04x addr 0x%04x", sub->net_idx, addr);
 
 	for (i = 0; i < ARRAY_SIZE(friend_cred); i++) {
-		struct bt_mesh_friend_cred *cred = &friend_cred[i];
+		struct friend_cred *cred = &friend_cred[i];
 
-		if (cred->net_idx != net_idx) {
+		if (cred->net_idx != sub->net_idx) {
 			continue;
 		}
 
@@ -366,15 +380,15 @@ int bt_mesh_friend_cred_get(u16_t net_idx, u16_t addr, u8_t idx,
 		}
 
 		if (nid) {
-			*nid = cred->cred[idx].nid;
+			*nid = cred->cred[sub->kr_flag].nid;
 		}
 
 		if (enc) {
-			*enc = cred->cred[idx].enc;
+			*enc = cred->cred[sub->kr_flag].enc;
 		}
 
 		if (priv) {
-			*priv = cred->cred[idx].privacy;
+			*priv = cred->cred[sub->kr_flag].privacy;
 		}
 
 		return 0;
@@ -383,8 +397,8 @@ int bt_mesh_friend_cred_get(u16_t net_idx, u16_t addr, u8_t idx,
 	return -ENOENT;
 }
 #else
-int bt_mesh_friend_cred_get(u16_t net_idx, u16_t addr, u8_t idx,
-			    u8_t *nid, const u8_t **enc, const u8_t **priv)
+int friend_cred_get(struct bt_mesh_subnet *sub, u16_t addr, u8_t *nid,
+		    const u8_t **enc, const u8_t **priv)
 {
 	return -ENOENT;
 }
@@ -430,7 +444,7 @@ int bt_mesh_net_create(u16_t idx, u8_t flags, const u8_t key[16],
 	struct bt_mesh_subnet *sub;
 	int err;
 
-	BT_DBG("idx %u iv_index %u", idx, iv_index);
+	BT_DBG("idx %u flags 0x%02x iv_index %u", idx, flags, iv_index);
 
 	BT_DBG("NetKey %s", bt_hex(key, 16));
 
@@ -438,11 +452,24 @@ int bt_mesh_net_create(u16_t idx, u8_t flags, const u8_t key[16],
 		return -EALREADY;
 	}
 
+	memset(msg_cache, 0, sizeof(msg_cache));
+	msg_cache_next = 0;
+
 	sub = &bt_mesh.sub[0];
 
-	err = bt_mesh_net_keys_create(&sub->keys[0], key);
-	if (err) {
-		return -EIO;
+	sub->kr_flag = BT_MESH_KEY_REFRESH(flags);
+	if (sub->kr_flag) {
+		err = bt_mesh_net_keys_create(&sub->keys[1], key);
+		if (err) {
+			return -EIO;
+		}
+
+		sub->kr_phase = BT_MESH_KR_PHASE_2;
+	} else {
+		err = bt_mesh_net_keys_create(&sub->keys[0], key);
+		if (err) {
+			return -EIO;
+		}
 	}
 
 	bt_mesh.valid = 1;
@@ -450,22 +477,20 @@ int bt_mesh_net_create(u16_t idx, u8_t flags, const u8_t key[16],
 
 	if ((MYNEWT_VAL(BLE_MESH_GATT_PROXY))) {
 		sub->node_id = BT_MESH_NODE_IDENTITY_RUNNING;
+		sub->node_id_start = k_uptime_get_32();
 	} else {
 		sub->node_id = BT_MESH_NODE_IDENTITY_NOT_SUPPORTED;
 	}
 
-	sub->kr_flag = BT_MESH_KEY_REFRESH(flags);
-	if (sub->kr_flag) {
-		memcpy(&sub->keys[1], &sub->keys[0], sizeof(sub->keys[0]));
-		sub->kr_phase = BT_MESH_KR_PHASE_2;
-	}
-
 	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();
 
+	/* Make sure we have valid beacon data to be sent */
+	bt_mesh_net_beacon_update(sub);
+
 	return 0;
 }
 
@@ -523,7 +548,7 @@ bool bt_mesh_kr_update(struct bt_mesh_subnet *sub, u8_t new_kr, bool new_key)
 			bt_mesh_net_revoke_keys(sub);
 			if ((MYNEWT_VAL(BLE_MESH_LOW_POWER)) ||
 			    (MYNEWT_VAL(BLE_MESH_FRIEND))) {
-				bt_mesh_friend_cred_refresh(sub->net_idx);
+				friend_cred_refresh(sub->net_idx);
 			}
 			sub->kr_phase = BT_MESH_KR_NORMAL;
 			return true;
@@ -553,7 +578,45 @@ void bt_mesh_rpl_reset(void)
 	}
 }
 
-bool bt_mesh_iv_update(u32_t iv_index, bool iv_update)
+#if MYNEWT_VAL(BLE_MESH_IV_UPDATE_TEST)
+void bt_mesh_iv_update_test(bool enable)
+{
+	bt_mesh.ivu_test = enable;
+}
+
+bool bt_mesh_iv_update(void)
+{
+	if (!bt_mesh_is_provisioned()) {
+		BT_ERR("Not yet provisioned");
+		return false;
+	}
+
+	if (bt_mesh.iv_update) {
+		bt_mesh_net_iv_update(bt_mesh.iv_index, false);
+	} else {
+		bt_mesh_net_iv_update(bt_mesh.iv_index + 1, true);
+	}
+
+	bt_mesh_net_sec_update(NULL);
+
+	return bt_mesh.iv_update;
+}
+#endif /* CONFIG_BT_MESH_IV_UPDATE_TEST */
+
+/* Used for sending immediate beacons to Friend queues and GATT clients */
+void bt_mesh_net_sec_update(struct bt_mesh_subnet *sub)
+{
+	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
+		bt_mesh_friend_sec_update(sub ? sub->net_idx : BT_MESH_KEY_ANY);
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) &&
+	    bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED) {
+		bt_mesh_proxy_beacon_send(sub);
+	}
+}
+
+bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update)
 {
 	int i;
 
@@ -571,12 +634,17 @@ bool bt_mesh_iv_update(u32_t iv_index, bool iv_update)
 			BT_DBG("Already in IV Update in Progress state");
 			return false;
 		}
-	} else  {
+	} else {
 		/* We're currently in Normal mode */
 
+		if (iv_index == bt_mesh.iv_index) {
+			BT_DBG("Same IV Index in normal mode");
+			return false;
+		}
+
 		if (iv_index < bt_mesh.iv_index ||
 		    iv_index > bt_mesh.iv_index + 42) {
-			BT_ERR("IV Index out of sync: " "0x%08x != 0x%08x",
+			BT_ERR("IV Index out of sync: 0x%08x != 0x%08x",
 			       iv_index, bt_mesh.iv_index);
 			return false;
 		}
@@ -590,14 +658,13 @@ bool bt_mesh_iv_update(u32_t iv_index, bool iv_update)
 		}
 
 		if (iv_index == bt_mesh.iv_index + 1 && !iv_update) {
-			BT_WARN("Ignoring new index in normal mode",
-				iv_index, bt_mesh.iv_index);
+			BT_WARN("Ignoring new index in normal mode");
 			return false;
 		}
 
 		if (!iv_update) {
 			/* Nothing to do */
-			BT_DBG("Already in Normal mode");
+			BT_DBG("Already in Normal state");
 			return false;
 		}
 
@@ -608,7 +675,7 @@ bool bt_mesh_iv_update(u32_t iv_index, bool iv_update)
 		}
 	}
 
-	if (!(MYNEWT_VAL(BLE_MESH_IV_UPDATE_TEST))) {
+	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)) {
@@ -651,28 +718,21 @@ bool bt_mesh_iv_update(u32_t iv_index, bool iv_update)
 		}
 	}
 
-	return false;
+	return true;
 }
 
 int bt_mesh_net_resend(struct bt_mesh_subnet *sub, struct os_mbuf *buf,
-		       bool new_key, bool friend_cred, bt_mesh_adv_func_t cb)
+		       bool new_key, const struct bt_mesh_send_cb *cb,
+		       void *cb_data)
 {
 	const u8_t *enc, *priv;
 	int err;
 
-	BT_DBG("net_idx 0x%04x, len %u", sub->net_idx, buf->om_len);
+	BT_DBG("net_idx 0x%04x new_key %u len %u", sub->net_idx, new_key,
+	       buf->om_len);
 
-	if (friend_cred) {
-		err = bt_mesh_friend_cred_get(sub->net_idx,
-					      BT_MESH_ADDR_UNASSIGNED,
-					      new_key, NULL, &enc, &priv);
-		if (err) {
-			return err;
-		}
-	} else {
-		enc = sub->keys[new_key].enc;
-		priv = sub->keys[new_key].privacy;
-	}
+	enc = sub->keys[new_key].enc;
+	priv = sub->keys[new_key].privacy;
 
 	err = bt_mesh_net_obfuscate(buf->om_data, BT_MESH_NET_IVI_TX, priv);
 	if (err) {
@@ -703,11 +763,12 @@ int bt_mesh_net_resend(struct bt_mesh_subnet *sub, struct os_mbuf *buf,
 		return err;
 	}
 
-	bt_mesh_adv_send(buf, cb);
+	bt_mesh_adv_send(buf, cb, cb_data);
 
 	if (!bt_mesh.iv_update && bt_mesh.seq > IV_UPDATE_SEQ_LIMIT) {
 		bt_mesh_beacon_ivu_initiator(true);
-		bt_mesh_iv_update(bt_mesh.iv_index + 1, true);
+		bt_mesh_net_iv_update(bt_mesh.iv_index + 1, true);
+		bt_mesh_net_sec_update(NULL);
 	}
 
 	return 0;
@@ -717,7 +778,7 @@ static void bt_mesh_net_local(struct os_event *work)
 {
 	struct os_mbuf *buf;
 
-	while ((buf = net_buf_get(&bt_mesh.local_queue, K_NO_WAIT))) {
+	while ((buf = net_buf_slist_get(&bt_mesh.local_queue))) {
 		BT_DBG("len %u: %s", buf->om_len, bt_hex(buf->om_data, buf->om_len));
 		bt_mesh_net_recv(buf, 0, BT_MESH_NET_IF_LOCAL);
 		net_buf_unref(buf);
@@ -758,32 +819,22 @@ int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
 		net_buf_simple_push_u8(buf, tx->ctx->send_ttl);
 	}
 
-	if (tx->sub->kr_phase == BT_MESH_KR_PHASE_2) {
-		if (tx->ctx->friend_cred) {
-			err = bt_mesh_friend_cred_get(tx->sub->net_idx,
-						      BT_MESH_ADDR_UNASSIGNED,
-						      1, &nid, &enc, &priv);
-			if (err) {
-				return err;
-			}
-		} else {
-			nid = tx->sub->keys[1].nid;
-			enc = tx->sub->keys[1].enc;
-			priv = tx->sub->keys[1].privacy;
+	if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && tx->friend_cred) {
+		if (friend_cred_get(tx->sub, BT_MESH_ADDR_UNASSIGNED,
+				    &nid, &enc, &priv)) {
+			BT_WARN("Falling back to master credentials");
+
+			tx->friend_cred = 0;
+
+			nid = tx->sub->keys[tx->sub->kr_flag].nid;
+			enc = tx->sub->keys[tx->sub->kr_flag].enc;
+			priv = tx->sub->keys[tx->sub->kr_flag].privacy;
 		}
 	} else {
-		if (tx->ctx->friend_cred) {
-			err = bt_mesh_friend_cred_get(tx->sub->net_idx,
-						      BT_MESH_ADDR_UNASSIGNED,
-						      0, &nid, &enc, &priv);
-			if (err) {
-				return err;
-			}
-		} else {
-			nid = tx->sub->keys[0].nid;
-			enc = tx->sub->keys[0].enc;
-			priv = tx->sub->keys[0].privacy;
-		}
+		tx->friend_cred = 0;
+		nid = tx->sub->keys[tx->sub->kr_flag].nid;
+		enc = tx->sub->keys[tx->sub->kr_flag].enc;
+		priv = tx->sub->keys[tx->sub->kr_flag].privacy;
 	}
 
 	net_buf_simple_push_u8(buf, (nid | (BT_MESH_NET_IVI_TX & 1) << 7));
@@ -797,7 +848,7 @@ int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
 }
 
 int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
-		     bt_mesh_adv_func_t cb)
+		     const struct bt_mesh_send_cb *cb, void *cb_data)
 {
 	int err;
 
@@ -807,16 +858,6 @@ int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
 	BT_DBG("Payload len %u: %s", buf->om_len, bt_hex(buf->om_data, buf->om_len));
 	BT_DBG("Seq 0x%06x", bt_mesh.seq);
 
-#if (MYNEWT_VAL(BLE_MESH_LOW_POWER))
-	/* Communication between LPN & Friend should always be using
-	 * the Friendship Credentials. Any other destination should
-	 * use the Master Credentials.
-	 */
-	if (bt_mesh_lpn_established()) {
-		tx->ctx->friend_cred = (tx->ctx->addr == bt_mesh.lpn.frnd);
-	}
-#endif
-
 	if (tx->ctx->send_ttl == BT_MESH_TTL_DEFAULT) {
 		tx->ctx->send_ttl = bt_mesh_default_ttl_get();
 	}
@@ -829,10 +870,27 @@ int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
 	BT_DBG("encoded %u bytes: %s", buf->om_len,
 		   bt_hex(buf->om_data, buf->om_len));
 
-	/* Deliver to GATT Proxy Clients if necessary */
-	if ((MYNEWT_VAL(BLE_MESH_GATT_PROXY))) {
+	/* Deliver to GATT Proxy Clients if necessary. Mesh spec 3.4.5.2:
+	 * "The output filter of the interface connected to advertising or
+	 * GATT bearers shall drop all messages with TTL value set to 1."
+	 */
+	if (MYNEWT_VAL(BLE_MESH_GATT_PROXY) &&
+	    tx->ctx->send_ttl != 1) {
 		if (bt_mesh_proxy_relay(buf, tx->ctx->addr) &&
 		    BT_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) {
+			/* Notify completion if this only went
+			 * through the Mesh Proxy.
+			 */
+			if (cb) {
+				if (cb->start) {
+					cb->start(0, 0, cb_data);
+				}
+
+				if (cb->end) {
+					cb->end(0, cb_data);
+				}
+			}
+
 			err = 0;
 			goto done;
 		}
@@ -841,13 +899,21 @@ int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
 	/* Deliver to local network interface if necessary */
 	if (bt_mesh_fixed_group_match(tx->ctx->addr) ||
 	    bt_mesh_elem_find(tx->ctx->addr)) {
-		net_buf_put(&bt_mesh.local_queue, net_buf_ref(buf));
-		if (cb) {
-			cb(buf, 0);
+		if (cb && cb->start) {
+			cb->start(0, 0, cb_data);
+		}
+		net_buf_slist_put(&bt_mesh.local_queue, net_buf_ref(buf));
+		if (cb && cb->end) {
+			cb->end(0, cb_data);
 		}
 		k_work_submit(&bt_mesh.local_work);
-	} else {
-		bt_mesh_adv_send(buf, cb);
+	} else if (tx->ctx->send_ttl != 1) {
+		/* Deliver to to the advertising network interface. Mesh spec
+		 * 3.4.5.2: "The output filter of the interface connected to
+		 * advertising or GATT bearers shall drop all messages with
+		 * TTL value set to 1."
+		 */
+		bt_mesh_adv_send(buf, cb, cb_data);
 	}
 
 done:
@@ -908,35 +974,12 @@ struct bt_mesh_subnet *bt_mesh_subnet_find(const u8_t net_id[8], u8_t flags,
 	return NULL;
 }
 
-static int net_decrypt(struct bt_mesh_subnet *sub, u8_t idx, const u8_t *data,
+static int net_decrypt(struct bt_mesh_subnet *sub, const u8_t *enc,
+		       const u8_t *priv, const u8_t *data,
 		       size_t data_len, struct bt_mesh_net_rx *rx,
 		       struct os_mbuf *buf)
 {
-	const u8_t *enc, *priv;
-
-	BT_DBG("NID 0x%02x, PDU NID 0x%02x net_idx 0x%04x idx %u",
-	       sub->keys[idx].nid, NID(data), sub->net_idx, idx);
-
-	if (NID(data) == sub->keys[idx].nid) {
-		enc = sub->keys[idx].enc;
-		priv = sub->keys[idx].privacy;
-		rx->ctx.friend_cred = 0;
-	} else {
-		u8_t nid;
-
-		if (bt_mesh_friend_cred_get(sub->net_idx,
-					    BT_MESH_ADDR_UNASSIGNED, idx,
-					    &nid, &enc, &priv)) {
-			return -ENOENT;
-		}
-
-		if (nid != NID(data)) {
-			return -ENOENT;
-		}
-
-		rx->ctx.friend_cred = 1;
-	}
-
+	BT_DBG("NID 0x%02x net_idx 0x%04x", NID(data), sub->net_idx);
 	BT_DBG("IVI %u net->iv_index 0x%08x", IVI(data), bt_mesh.iv_index);
 
 	rx->old_iv = (IVI(data) != (bt_mesh.iv_index & 0x01));
@@ -970,24 +1013,73 @@ static int net_decrypt(struct bt_mesh_subnet *sub, u8_t idx, const u8_t *data,
 	return bt_mesh_net_decrypt(enc, buf, BT_MESH_NET_IVI_RX(rx), false);
 }
 
-static int net_find_and_decrypt(const u8_t *data, size_t data_len,
-				struct bt_mesh_net_rx *rx,
-				struct os_mbuf *buf)
+#if (MYNEWT_VAL(BLE_MESH_LOW_POWER) || \
+     MYNEWT_VAL(BLE_MESH_FRIEND))
+static int friend_decrypt(struct bt_mesh_subnet *sub, const u8_t *data,
+			  size_t data_len, struct bt_mesh_net_rx *rx,
+			  struct os_mbuf *buf)
 {
 	int i;
 
+	BT_DBG("NID 0x%02x net_idx 0x%04x", NID(data), sub->net_idx);
+
+	for (i = 0; i < ARRAY_SIZE(friend_cred); i++) {
+		struct friend_cred *cred = &friend_cred[i];
+
+		if (cred->net_idx != sub->net_idx) {
+			continue;
+		}
+
+		if (NID(data) == cred->cred[0].nid &&
+		    !net_decrypt(sub, cred->cred[0].enc, cred->cred[0].privacy,
+				 data, data_len, rx, buf)) {
+			return 0;
+		}
+
+		if (sub->kr_phase == BT_MESH_KR_NORMAL) {
+			continue;
+		}
+
+		if (NID(data) == cred->cred[1].nid &&
+		    !net_decrypt(sub, cred->cred[1].enc, cred->cred[1].privacy,
+				 data, data_len, rx, buf)) {
+			rx->new_key = 1;
+			return 0;
+		}
+	}
+
+	return -ENOENT;
+}
+#endif
+
+static bool net_find_and_decrypt(const u8_t *data, size_t data_len,
+				 struct bt_mesh_net_rx *rx,
+				 struct os_mbuf *buf)
+{
+	struct bt_mesh_subnet *sub;
+	unsigned int i;
+
 	BT_DBG("");
 
 	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
-		struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
-		int err;
-
+		sub = &bt_mesh.sub[i];
 		if (sub->net_idx == BT_MESH_KEY_UNUSED) {
 			continue;
 		}
 
-		err = net_decrypt(sub, 0, data, data_len, rx, buf);
-		if (!err) {
+#if (MYNEWT_VAL(BLE_MESH_LOW_POWER) || \
+     MYNEWT_VAL(BLE_MESH_FRIEND))
+		if (!friend_decrypt(sub, data, data_len, rx, buf)) {
+			rx->friend_cred = 1;
+			rx->ctx.net_idx = sub->net_idx;
+			rx->sub = sub;
+			return true;
+		}
+#endif
+
+		if (NID(data) == sub->keys[0].nid &&
+		    !net_decrypt(sub, sub->keys[0].enc, sub->keys[0].privacy,
+				 data, data_len, rx, buf)) {
 			rx->ctx.net_idx = sub->net_idx;
 			rx->sub = sub;
 			return true;
@@ -997,11 +1089,12 @@ static int net_find_and_decrypt(const u8_t *data, size_t data_len,
 			continue;
 		}
 
-		err = net_decrypt(sub, 1, data, data_len, rx, buf);
-		if (!err) {
+		if (NID(data) == sub->keys[1].nid &&
+		    !net_decrypt(sub, sub->keys[1].enc, sub->keys[1].privacy,
+				 data, data_len, rx, buf)) {
+			rx->new_key = 1;
 			rx->ctx.net_idx = sub->net_idx;
 			rx->sub = sub;
-			rx->new_key = 1;
 			return true;
 		}
 	}
@@ -1009,6 +1102,25 @@ static int net_find_and_decrypt(const u8_t *data, size_t data_len,
 	return false;
 }
 
+/* Relaying from advertising to the advertising bearer should only happen
+ * if the Relay state is set to enabled. Locally originated packets always
+ * get sent to the advertising bearer. If the packet came in through GATT,
+ * then we should only relay it if the GATT Proxy state is enabled.
+ */
+static bool relay_to_adv(enum bt_mesh_net_if net_if)
+{
+	switch (net_if) {
+	case BT_MESH_NET_IF_LOCAL:
+		return true;
+	case BT_MESH_NET_IF_ADV:
+		return (bt_mesh_relay_get() == BT_MESH_RELAY_ENABLED);
+	case BT_MESH_NET_IF_PROXY:
+		return (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED);
+	default:
+		return false;
+	}
+}
+
 static void bt_mesh_net_relay(struct os_mbuf *sbuf,
 			      struct bt_mesh_net_rx *rx)
 {
@@ -1016,62 +1128,65 @@ static void bt_mesh_net_relay(struct os_mbuf *sbuf,
 	struct os_mbuf *buf;
 	u8_t nid, transmit;
 
-	BT_DBG("TTL %u CTL %u dst 0x%04x", rx->ctx.recv_ttl, CTL(sbuf->om_data),
-	       rx->dst);
+	if (rx->net_if == BT_MESH_NET_IF_LOCAL) {
+		/* Locally originated PDUs with TTL=1 will only be delivered
+		 * to local elements as per Mesh Profile 1.0 section 3.4.5.2:
+		 * "The output filter of the interface connected to
+		 * advertising or GATT bearers shall drop all messages with
+		 * TTL value set to 1."
+		 */
+		if (rx->ctx.recv_ttl == 1) {
+			return;
+		}
+	} else {
+		if (rx->ctx.recv_ttl <= 1) {
+			return;
+		}
+	}
 
-	if (rx->net_if != BT_MESH_NET_IF_LOCAL && rx->ctx.recv_ttl <= 1) {
+	if (rx->net_if == BT_MESH_NET_IF_ADV &&
+	    bt_mesh_relay_get() != BT_MESH_RELAY_ENABLED &&
+	    bt_mesh_gatt_proxy_get() != BT_MESH_GATT_PROXY_ENABLED) {
 		return;
 	}
 
-	transmit = bt_mesh_relay_retransmit_get();
-	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, TRANSMIT_COUNT(transmit),
-				 TRANSMIT_INT(transmit), K_NO_WAIT);
+	BT_DBG("TTL %u CTL %u dst 0x%04x", rx->ctx.recv_ttl, rx->ctl, rx->dst);
+
+	/* The Relay Retransmit state is only applied to adv-adv relaying.
+	 * Anything else (like GATT to adv, or locally originated packets)
+	 * use the Network Transmit state.
+	 */
+	if (rx->net_if == BT_MESH_NET_IF_ADV) {
+		transmit = bt_mesh_relay_retransmit_get();
+	} else {
+		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);
 	if (!buf) {
 		BT_ERR("Out of relay buffers");
 		return;
 	}
 
-	net_buf_add_mem(buf, sbuf->om_data, sbuf->om_len);
-
 	/* Only decrement TTL for non-locally originated packets */
 	if (rx->net_if != BT_MESH_NET_IF_LOCAL) {
 		/* Leave CTL bit intact */
-		buf->om_data[1] &= 0x80;
-		buf->om_data[1] |= rx->ctx.recv_ttl - 1;
+		sbuf->om_data[1] &= 0x80;
+		sbuf->om_data[1] |= rx->ctx.recv_ttl - 1;
 	}
 
-	if (rx->sub->kr_phase == BT_MESH_KR_PHASE_2) {
-		if (bt_mesh_friend_dst_is_lpn(rx->dst)) {
-			if (bt_mesh_friend_cred_get(rx->sub->net_idx,
-						    BT_MESH_ADDR_UNASSIGNED,
-						    1, &nid, &enc, &priv)) {
-				BT_ERR("friend_cred_get failed");
-				goto done;
-			}
-		} else {
-			enc = rx->sub->keys[1].enc;
-			priv = rx->sub->keys[1].privacy;
-			nid = rx->sub->keys[1].nid;
-		}
-	} else {
-		if (bt_mesh_friend_dst_is_lpn(rx->dst)) {
-			if (bt_mesh_friend_cred_get(rx->sub->net_idx,
-						    BT_MESH_ADDR_UNASSIGNED,
-						    0, &nid, &enc, &priv)) {
-				BT_ERR("friend_cred_get failed");
-				goto done;
-			}
-		} else  {
-			enc = rx->sub->keys[0].enc;
-			priv = rx->sub->keys[0].privacy;
-			nid = rx->sub->keys[0].nid;
-		}
-	}
+	net_buf_add_mem(buf, sbuf->om_data, sbuf->om_len);
+
+	enc = rx->sub->keys[rx->sub->kr_flag].enc;
+	priv = rx->sub->keys[rx->sub->kr_flag].privacy;
+	nid = rx->sub->keys[rx->sub->kr_flag].nid;
 
 	BT_DBG("Relaying packet. TTL is now %u", TTL(buf->om_data));
 
-	/* Update NID if RX or TX is with friend credentials */
-	if (rx->ctx.friend_cred || bt_mesh_friend_dst_is_lpn(rx->dst)) {
+	/* Update NID if RX or RX was with friend credentials */
+	if (rx->friend_cred) {
 		buf->om_data[0] &= 0x80; /* Clear everything except IVI */
 		buf->om_data[0] |= nid;
 	}
@@ -1091,25 +1206,22 @@ static void bt_mesh_net_relay(struct os_mbuf *sbuf,
 	}
 
 	BT_DBG("encoded %u bytes: %s", buf->om_len,
-		   bt_hex(buf->om_data, buf->om_len));
-
-	if ((MYNEWT_VAL(BLE_MESH_FRIEND))) {
-		if (bt_mesh_friend_enqueue(buf, rx->dst) &&
-		    BT_MESH_ADDR_IS_UNICAST(rx->dst)) {
-			goto done;
-		}
-	}
+	       bt_hex(buf->om_data, buf->om_len));
 
-	if ((MYNEWT_VAL(BLE_MESH_GATT_PROXY))) {
+	/* Sending to the GATT bearer should only happen if GATT Proxy
+	 * is enabled or the message originates from the local node.
+	 */
+	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)) {
 			goto done;
 		}
 	}
 
-	if (rx->net_if != BT_MESH_NET_IF_ADV ||
-	    bt_mesh_relay_get() == BT_MESH_RELAY_ENABLED) {
-		bt_mesh_adv_send(buf, NULL);
+	if (relay_to_adv(rx->net_if)) {
+		bt_mesh_adv_send(buf, NULL, NULL);
 	}
 
 done:
@@ -1119,7 +1231,7 @@ static void bt_mesh_net_relay(struct os_mbuf *sbuf,
 int bt_mesh_net_decode(struct os_mbuf *data, enum bt_mesh_net_if net_if,
 		       struct bt_mesh_net_rx *rx, struct os_mbuf *buf)
 {
-	if (data->om_len < 18) {
+	if (data->om_len < BT_MESH_NET_MIN_PDU_LEN) {
 		BT_WARN("Dropping too short mesh packet (len %u)", data->om_len);
 		BT_WARN("%s", bt_hex(data->om_data, data->om_len));
 		return -EINVAL;
@@ -1186,8 +1298,8 @@ void bt_mesh_net_recv(struct os_mbuf *data, s8_t rssi,
 		      enum bt_mesh_net_if net_if)
 {
 	struct os_mbuf *buf = NET_BUF_SIMPLE(29);
+	struct bt_mesh_net_rx rx = { .rssi = rssi };
 	struct net_buf_simple_state state;
-	struct bt_mesh_net_rx rx;
 
 	BT_DBG("rssi %d net_if %u", rssi, net_if);
 
@@ -1200,25 +1312,28 @@ void bt_mesh_net_recv(struct os_mbuf *data, s8_t rssi,
 		goto done;
 	}
 
+	/* Save the state so the buffer can later be relayed */
+	net_buf_simple_save(buf, &state);
+
 	if ((MYNEWT_VAL(BLE_MESH_GATT_PROXY)) &&
 	    net_if == BT_MESH_NET_IF_PROXY) {
 		bt_mesh_proxy_addr_add(data, rx.ctx.addr);
 	}
 
-	/* Save parsing state so the buffer can later be relayed */
-	net_buf_simple_save(buf, &state);
+	rx.local_match = (bt_mesh_fixed_group_match(rx.dst) ||
+			  bt_mesh_elem_find(rx.dst));
 
-	if (bt_mesh_fixed_group_match(rx.dst) || bt_mesh_elem_find(rx.dst)) {
-		bt_mesh_trans_recv(buf, &rx);
+	bt_mesh_trans_recv(buf, &rx);
 
-		if (BT_MESH_ADDR_IS_UNICAST(rx.dst)) {
-			goto done;
-		}
+	/* 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) ||
+	    (!rx.local_match && !rx.friend_match)) {
+		net_buf_simple_restore(buf, &state);
+		bt_mesh_net_relay(buf, &rx);
 	}
 
-	net_buf_simple_restore(buf, &state);
-	bt_mesh_net_relay(buf, &rx);
-
 done:
     os_mbuf_free_chain(buf);
 }
@@ -1228,13 +1343,12 @@ static void ivu_complete(struct os_event *work)
 	BT_DBG("");
 
 	bt_mesh_beacon_ivu_initiator(true);
-	bt_mesh_iv_update(bt_mesh.iv_index, false);
+	bt_mesh_net_iv_update(bt_mesh.iv_index, false);
 }
 
 void bt_mesh_net_init(void)
 {
 	k_delayed_work_init(&bt_mesh.ivu_complete, ivu_complete);
 
-	os_eventq_init(&bt_mesh.local_queue);
 	k_work_init(&bt_mesh.local_work, bt_mesh_net_local);
 }
diff --git a/net/nimble/host/mesh/src/net.h b/net/nimble/host/mesh/src/net.h
index af05583dd..a99b75ac2 100644
--- a/net/nimble/host/mesh/src/net.h
+++ b/net/nimble/host/mesh/src/net.h
@@ -22,89 +22,102 @@
 
 #include <stdbool.h>
 #include "mesh/mesh.h"
-
-struct bt_mesh_app_key
-{
-    u16_t net_idx;
-    u16_t app_idx;bool updated;
-    struct bt_mesh_app_keys
-    {
-        u8_t id;
-        u8_t val[16];
-    } keys[2];
+#include "mesh/slist.h"
+
+struct bt_mesh_app_key {
+	u16_t net_idx;
+	u16_t app_idx;
+	bool  updated;
+	struct bt_mesh_app_keys {
+		u8_t id;
+		u8_t val[16];
+	} keys[2];
 };
 
-/* Friendship Credentials */
-struct bt_mesh_friend_cred
-{
-    u16_t net_idx;
-    u16_t addr;
-
-    u16_t lpn_counter;
-    u16_t frnd_counter;
-
-    struct
-    {
-        u8_t nid; /* NID */
-        u8_t enc[16]; /* EncKey */
-        u8_t privacy[16]; /* PrivacyKey */
-    } cred[2];
-};
+struct bt_mesh_subnet {
+	u32_t beacon_sent;        /* Timestamp of last sent beacon */
+	u8_t  beacons_last;       /* Number of beacons during last
+				   * observation window
+				   */
+	u8_t  beacons_cur;        /* Number of beaconds observed during
+				   * currently ongoing window.
+				   */
 
-struct bt_mesh_subnet
-{
-    s64_t beacon_sent; /* Time stamp of last sent beacon */
-    u8_t beacons_last; /* Number of beacons during last
-     * observation window
-     */
-    u8_t beacons_cur; /* Number of beaconds observed during
-     * currently ongoing window.
-     */
+	u8_t  beacon_cache[21];   /* Cached last authenticated beacon */
 
-    u16_t net_idx; /* NetKeyIndex */
+	u16_t net_idx;            /* NetKeyIndex */
 
-    bool kr_flag; /* Key Refresh Flag */
-    u8_t kr_phase; /* Key Refresh Phase */
+	bool  kr_flag;            /* Key Refresh Flag */
+	u8_t  kr_phase;           /* Key Refresh Phase */
 
-    u8_t node_id; /* Node Identity State */
+	u8_t  node_id;            /* Node Identity State */
+	u32_t node_id_start;      /* Node Identity started timestamp */
 
-    u8_t auth[8]; /* Beacon Authentication Value */
+	u8_t  auth[8];            /* Beacon Authentication Value */
 
-    struct bt_mesh_subnet_keys
-    {
-        u8_t net[16]; /* NetKey */
-        u8_t nid; /* NID */
-        u8_t enc[16]; /* EncKey */
-        u8_t net_id[8]; /* Network ID */
+	struct bt_mesh_subnet_keys {
+		u8_t net[16];       /* NetKey */
+		u8_t nid;           /* NID */
+		u8_t enc[16];       /* EncKey */
+		u8_t net_id[8];     /* Network ID */
 #if (MYNEWT_VAL(BLE_MESH_GATT_PROXY))
-        u8_t identity[16]; /* IdentityKey */
+		u8_t identity[16];  /* IdentityKey */
 #endif
-        u8_t privacy[16]; /* PrivacyKey */
-        u8_t beacon[16]; /* BeaconKey */
-    } keys[2];
+		u8_t privacy[16];   /* PrivacyKey */
+		u8_t beacon[16];    /* BeaconKey */
+	} keys[2];
 };
 
-struct bt_mesh_rpl
-{
-    u16_t src;bool old_iv;
-    u32_t seq;
+struct bt_mesh_rpl {
+	u16_t src;
+	bool  old_iv;
+	u32_t seq;
 };
 
-struct bt_mesh_friend
-{
-    u16_t lpn;
-    u8_t recv_delay;
-    u8_t fsn :1, send_last :1, send_offer :1, send_update :1;
-    s32_t poll_to;
-    u16_t lpn_counter;
-    u16_t counter;
-    s8_t rssi;
-
-    struct k_delayed_work timer;
-
-    struct os_mbuf *last;
-    /* TU BYLO KFIFO*/
-    struct os_eventq queue;
+#if MYNEWT_VAL(BLE_MESH_FRIEND)
+#define FRIEND_SEG_RX MYNEWT_VAL(BLE_MESH_FRIEND_SEG_RX)
+#define FRIEND_SUB_LIST_SIZE MYNEWT_VAL(BLE_MESH_FRIEND_SUB_LIST_SIZE)
+#else
+#define FRIEND_SEG_RX 0
+#define FRIEND_SUB_LIST_SIZE 0
+#endif
+
+struct bt_mesh_friend {
+	u16_t lpn;
+	u8_t  recv_delay;
+	u8_t  fsn:1,
+	      send_last:1,
+	      pending_req:1,
+	      sec_update:1,
+	      pending_buf:1,
+	      valid:1,
+	      established:1;
+	s32_t poll_to;
+	u16_t lpn_counter;
+	u16_t counter;
+
+	u16_t net_idx;
+
+	u16_t sub_list[FRIEND_SUB_LIST_SIZE];
+
+	struct k_delayed_work timer;
+
+	struct bt_mesh_friend_seg {
+		sys_slist_t queue;
+	} seg[FRIEND_SEG_RX];
+
+	struct os_mbuf *last;
+
+	sys_slist_t queue;
+	u32_t queue_size;
+
+	/* Friend Clear Procedure */
+	struct {
+		u32_t start;                  /* Clear Procedure start */
+		u16_t frnd;                   /* Previous Friend's address */
+		u16_t repeat_sec;             /* Repeat timeout in seconds */
+		struct k_delayed_work timer;  /* Repeat timer */
+	} clear;
 };
 
 #if (MYNEWT_VAL(BLE_MESH_LOW_POWER))
@@ -114,123 +127,136 @@ struct bt_mesh_friend
 #endif
 
 /* Low Power Node state */
-struct bt_mesh_lpn
-{
-    enum __packed
-    {
-        BT_MESH_LPN_DISABLED, /* LPN feature is disabled */
-        BT_MESH_LPN_CLEAR, /* Clear in progress */
-        BT_MESH_LPN_ENABLED, /* LPN enabled, but no Friend */
-        BT_MESH_LPN_WAIT_OFFER, /* Friend Req sent */
-        BT_MESH_LPN_ESTABLISHING, /* First Friend Poll sent */
-        BT_MESH_LPN_ESTABLISHED, /* Friendship established */
-        BT_MESH_LPN_RECV_DELAY, /* Poll sent, waiting ReceiveDelay */
-        BT_MESH_LPN_WAIT_UPDATE, /* Waiting for Update or message */
-    } state;
-
-    /* Transaction Number (used for subscription list) */
-    u8_t xact_next;
-    u8_t xact_pending;
-    u8_t sent_req;
-
-    /* Address of our Friend when we're a LPN. Unassigned if we don't
-     * have a friend yet.
-     */
-    u16_t frnd;
-
-    /* Value from the friend offer */
-    u8_t recv_win;
-
-    u8_t req_attempts; /* Number of Request attempts */
-
-    s32_t poll_timeout;
-
-    u8_t groups_changed :1, /* Friend Subscription List needs updating */
-    pending_poll :1, /* Poll to be sent after subscription */
-    disable :1, /* Disable LPN after clearing */
-    fsn :1; /* Friend Sequence Number */
-
-    /* Friend Queue Size */
-    u8_t queue_size;
-
-    /* LPNCounter */
-    u16_t counter;
-
-    /* Next LPN related action timer */
-    struct k_delayed_work timer;
-
-    /* Subscribed groups */
-    u16_t groups[LPN_GROUPS];
-
-    /* Bit fields for tracking which groups the Friend knows about */
-    ATOMIC_DEFINE(added, LPN_GROUPS);ATOMIC_DEFINE(pending, LPN_GROUPS);ATOMIC_DEFINE(to_remove, LPN_GROUPS);
+struct bt_mesh_lpn {
+	enum __packed {
+		BT_MESH_LPN_DISABLED,     /* LPN feature is disabled */
+		BT_MESH_LPN_CLEAR,        /* Clear in progress */
+		BT_MESH_LPN_TIMER,        /* Waiting for auto timer expiry */
+		BT_MESH_LPN_ENABLED,      /* LPN enabled, but no Friend */
+		BT_MESH_LPN_REQ_WAIT,     /* Wait before scanning for offers */
+		BT_MESH_LPN_WAIT_OFFER,   /* Friend Req sent */
+		BT_MESH_LPN_ESTABLISHED,  /* Friendship established */
+		BT_MESH_LPN_RECV_DELAY,   /* Poll sent, waiting ReceiveDelay */
+		BT_MESH_LPN_WAIT_UPDATE,  /* Waiting for Update or message */
+	} state;
+
+	/* Transaction Number (used for subscription list) */
+	u8_t xact_next;
+	u8_t xact_pending;
+	u8_t sent_req;
+
+	/* Address of our Friend when we're a LPN. Unassigned if we don't
+	 * have a friend yet.
+	 */
+	u16_t frnd;
+
+	/* Value from the friend offer */
+	u8_t  recv_win;
+
+	u8_t  req_attempts;     /* Number of Request attempts */
+
+	s32_t poll_timeout;
+
+	u8_t  groups_changed:1, /* Friend Subscription List needs updating */
+	      pending_poll:1,   /* Poll to be sent after subscription */
+	      disable:1,        /* Disable LPN after clearing */
+	      fsn:1,            /* Friend Sequence Number */
+	      established:1,    /* Friendship established */
+	      clear_success:1;  /* Friend Clear Confirm received */
+
+	/* Friend Queue Size */
+	u8_t  queue_size;
+
+	/* LPNCounter */
+	u16_t counter;
+
+	/* Previous Friend of this LPN */
+	u16_t old_friend;
+
+	/* Duration reported for last advertising packet */
+	u16_t adv_duration;
+
+	/* Next LPN related action timer */
+	struct k_delayed_work timer;
+
+	/* Subscribed groups */
+	u16_t groups[LPN_GROUPS];
+
+	/* Bit fields for tracking which groups the Friend knows about */
+	ATOMIC_DEFINE(added, LPN_GROUPS);
+	ATOMIC_DEFINE(pending, LPN_GROUPS);
+	ATOMIC_DEFINE(to_remove, LPN_GROUPS);
 };
 
-struct bt_mesh_net
-{
-    u32_t iv_index; /* Current IV Index */
-    u32_t seq :24, /* Next outgoing sequence number */
-    iv_update :1, /* 1 if IV Update in Progress */
-    ivu_initiator :1, /* IV Update initiated by us */
-    pending_update :1, /* Update blocked by SDU in progress */
-    valid :1; /* 0 if unused */
+struct bt_mesh_net {
+	u32_t iv_index;          /* Current IV Index */
+	u32_t seq:24,            /* Next outgoing sequence number */
+	      iv_update:1,       /* 1 if IV Update in Progress */
+	      ivu_initiator:1,   /* IV Update initiated by us */
+	      ivu_test:1,        /* IV Update test mode */
+	      pending_update:1,  /* Update blocked by SDU in progress */
+	      valid:1;           /* 0 if unused */
 
-    s64_t last_update; /* Time since last IV Update change */
+	s64_t last_update;       /* Time since last IV Update change */
 
-    /* Local network interface */
-    struct os_callout local_work;
-    struct os_eventq local_queue;
-    struct os_event local_ev;
+	/* Local network interface */
+	struct os_callout local_work;
+	sys_slist_t local_queue;
 
 #if MYNEWT_VAL(BLE_MESH_FRIEND)
-    struct bt_mesh_friend frnd; /* Friend state */
+	/* Friend state, unique for each LPN that we're Friends for */
+	struct bt_mesh_friend frnd[MYNEWT_VAL(BLE_MESH_FRIEND_LPN_COUNT)];
 #endif
 
 #if (MYNEWT_VAL(BLE_MESH_LOW_POWER))
-    struct bt_mesh_lpn lpn; /* Low Power Node state */
+	struct bt_mesh_lpn lpn;  /* Low Power Node state */
 #endif
 
-    /* Timer to transition IV Update in Progress state */
-    struct k_delayed_work ivu_complete;
+	/* Timer to transition IV Update in Progress state */
+	struct k_delayed_work ivu_complete;
 
-    u8_t dev_key[16];
+	u8_t dev_key[16];
 
-    struct bt_mesh_app_key app_keys[MYNEWT_VAL(BLE_MESH_APP_KEY_COUNT)];
+	struct bt_mesh_app_key app_keys[MYNEWT_VAL(BLE_MESH_APP_KEY_COUNT)];
 
-    struct bt_mesh_subnet sub[MYNEWT_VAL(BLE_MESH_SUBNET_COUNT)];
+	struct bt_mesh_subnet sub[MYNEWT_VAL(BLE_MESH_SUBNET_COUNT)];
 
-    struct bt_mesh_rpl rpl[MYNEWT_VAL(BLE_MESH_CRPL)];
+	struct bt_mesh_rpl rpl[MYNEWT_VAL(BLE_MESH_CRPL)];
 };
 
 /* Network interface */
-enum bt_mesh_net_if
-{
-    BT_MESH_NET_IF_ADV,
-    BT_MESH_NET_IF_LOCAL,
-    BT_MESH_NET_IF_PROXY,
-    BT_MESH_NET_IF_PROXY_CFG,
+enum bt_mesh_net_if {
+	BT_MESH_NET_IF_ADV,
+	BT_MESH_NET_IF_LOCAL,
+	BT_MESH_NET_IF_PROXY,
+	BT_MESH_NET_IF_PROXY_CFG,
 };
 
 /* Decoding context for Network/Transport data */
-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 */
-    ctl :1, /* Network Control */
-    net_if :2; /* Network interface */
-    s8_t rssi;
+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 */
+	       ctl:1,          /* Network Control */
+	       net_if:2,       /* Network interface */
+	       local_match:1,  /* Matched a local element */
+	       friend_match:1; /* Matched an LPN we're friends for */
+	s8_t   rssi;
 };
 
 /* Encoding context for Network/Transport data */
-struct bt_mesh_net_tx
-{
-    struct bt_mesh_subnet *sub;
-    struct bt_mesh_msg_ctx *ctx;
-    u16_t src;
+struct bt_mesh_net_tx {
+	struct bt_mesh_subnet *sub;
+	struct bt_mesh_msg_ctx *ctx;
+	u16_t src;
+	u8_t  xmit;
+	u8_t  friend_cred:1,
+	      aszmic:1,
+	      aid:6;
 };
 
 extern struct bt_mesh_net bt_mesh;
@@ -240,75 +266,73 @@ extern struct bt_mesh_net bt_mesh;
 
 #define BT_MESH_NET_HDR_LEN 9
 
-int
-bt_mesh_net_keys_create(struct bt_mesh_subnet_keys *keys, const u8_t key[16]);
+int bt_mesh_net_keys_create(struct bt_mesh_subnet_keys *keys,
+			    const u8_t key[16]);
 
-int
-bt_mesh_net_create(u16_t idx, u8_t flags, const u8_t key[16], u32_t iv_index);
+int bt_mesh_net_create(u16_t idx, u8_t flags, const u8_t key[16],
+		       u32_t iv_index);
 
-u8_t
-bt_mesh_net_flags(struct bt_mesh_subnet *sub);
+u8_t bt_mesh_net_flags(struct bt_mesh_subnet *sub);
 
-int bt_mesh_friend_cred_get(u16_t net_idx, u16_t addr, u8_t idx,
-			    u8_t *nid, const u8_t **enc, const u8_t **priv);
-int
-bt_mesh_friend_cred_set(struct bt_mesh_friend_cred *cred, u8_t idx,
-                        const u8_t net_key[16]);
-void
-bt_mesh_friend_cred_refresh(u16_t net_idx);
-int
-bt_mesh_friend_cred_update(u16_t net_idx, u8_t idx, const u8_t net_key[16]);
-struct bt_mesh_friend_cred *
-bt_mesh_friend_cred_add(u16_t net_idx, const u8_t net_key[16], u8_t idx,
-                        u16_t addr, u16_t lpn_counter, u16_t frnd_counter);
-void
-bt_mesh_friend_cred_clear(struct bt_mesh_friend_cred *cred);
-int
-bt_mesh_friend_cred_del(u16_t net_idx, u16_t addr);
+bool bt_mesh_kr_update(struct bt_mesh_subnet *sub, u8_t new_kr, bool new_key);
 
-bool
-bt_mesh_kr_update(struct bt_mesh_subnet *sub, u8_t new_kr, bool new_key);
+void bt_mesh_net_revoke_keys(struct bt_mesh_subnet *sub);
 
-void
-bt_mesh_net_revoke_keys(struct bt_mesh_subnet *sub);
+int bt_mesh_net_beacon_update(struct bt_mesh_subnet *sub);
 
-int
-bt_mesh_net_beacon_update(struct bt_mesh_subnet *sub);
+void bt_mesh_rpl_reset(void);
 
-void
-bt_mesh_rpl_reset(void);
+bool bt_mesh_net_iv_update(u32_t iv_index, bool iv_update);
 
-bool
-bt_mesh_iv_update(u32_t iv_index, bool iv_update);
+void bt_mesh_net_sec_update(struct bt_mesh_subnet *sub);
 
-struct bt_mesh_subnet *
-bt_mesh_subnet_get(u16_t net_idx);
+struct bt_mesh_subnet *bt_mesh_subnet_get(u16_t net_idx);
 
-struct bt_mesh_subnet *
-bt_mesh_subnet_find(const u8_t net_id[8], u8_t flags, u32_t iv_index,
-                    const u8_t auth[8], bool *new_key);
+struct bt_mesh_subnet *bt_mesh_subnet_find(const u8_t net_id[8], u8_t flags,
+					   u32_t iv_index, const u8_t auth[8],
+					   bool *new_key);
 
-int
-bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
-bool proxy);
+int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
+		       bool proxy);
 
-int
-bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
-                 bt_mesh_adv_func_t cb);
+int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct os_mbuf *buf,
+		     const struct bt_mesh_send_cb *cb, void *cb_data);
 
-int
-bt_mesh_net_resend(struct bt_mesh_subnet *sub, struct os_mbuf *buf,
-bool new_key,
-                   bool friend_cred, bt_mesh_adv_func_t cb);
+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);
 
-int
-bt_mesh_net_decode(struct os_mbuf *data, enum bt_mesh_net_if net_if,
-                   struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
+int bt_mesh_net_decode(struct os_mbuf *data, enum bt_mesh_net_if net_if,
+		       struct bt_mesh_net_rx *rx, struct os_mbuf *buf);
 
-void
-bt_mesh_net_recv(struct os_mbuf *data, s8_t rssi, 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);
+
+void bt_mesh_net_init(void);
+
+/* Friendship Credential Management */
+struct friend_cred {
+	u16_t net_idx;
+	u16_t addr;
+
+	u16_t lpn_counter;
+	u16_t frnd_counter;
+
+	struct {
+		u8_t nid;         /* NID */
+		u8_t enc[16];     /* EncKey */
+		u8_t privacy[16]; /* PrivacyKey */
+	} cred[2];
+};
 
-void
-bt_mesh_net_init(void);
+int friend_cred_get(struct bt_mesh_subnet *sub, u16_t addr, u8_t *nid,
+			    const u8_t **enc, const u8_t **priv);
+int friend_cred_set(struct friend_cred *cred, u8_t idx, const u8_t net_key[16]);
+void friend_cred_refresh(u16_t net_idx);
+int friend_cred_update(struct bt_mesh_subnet *sub);
+struct friend_cred *friend_cred_create(struct bt_mesh_subnet *sub, u16_t addr,
+				       u16_t lpn_counter, u16_t frnd_counter);
+void friend_cred_clear(struct friend_cred *cred);
+int friend_cred_del(u16_t net_idx, u16_t addr);
 
 #endif
diff --git a/net/nimble/host/mesh/src/prov.c b/net/nimble/host/mesh/src/prov.c
index fcad1386e..ac4702b41 100644
--- a/net/nimble/host/mesh/src/prov.c
+++ b/net/nimble/host/mesh/src/prov.c
@@ -129,6 +129,7 @@ struct prov_link {
 	u8_t  conf_inputs[145];  /* ConfirmationInputs */
 	u8_t  prov_salt[16];     /* Provisioning Salt */
 
+#if (MYNEWT_VAL(BLE_MESH_PB_ADV))
 	u32_t id;                /* Link ID */
 
 	struct {
@@ -140,7 +141,6 @@ struct prov_link {
 		struct os_mbuf *buf;
 	} rx;
 
-#if (MYNEWT_VAL(BLE_MESH_PB_ADV))
 	struct {
 		/* Start timestamp of the transaction */
 		s64_t start;
@@ -183,9 +183,9 @@ static const struct bt_mesh_prov *prov;
 static void close_link(u8_t err, u8_t reason);
 
 #if (MYNEWT_VAL(BLE_MESH_PB_ADV))
-static void buf_sent(struct os_mbuf *buf, int err)
+static void buf_sent(int err, void *user_data)
 {
-    BT_DBG("buf_sent");
+	BT_DBG("buf_sent");
 
 	if (!link.tx.buf[0]) {
 		return;
@@ -194,6 +194,10 @@ static void buf_sent(struct os_mbuf *buf, int err)
 	k_delayed_work_submit(&link.tx.retransmit, RETRANSMIT_TIMEOUT);
 }
 
+static struct bt_mesh_send_cb buf_sent_cb = {
+	.end = buf_sent,
+};
+
 static void free_segments(void)
 {
 	int i;
@@ -223,10 +227,14 @@ static void prov_clear_tx(void)
 
 static void reset_link(void)
 {
-    atomic_clear_bit(link.flags, LINK_ACTIVE);
+	atomic_clear_bit(link.flags, LINK_ACTIVE);
 
 	prov_clear_tx();
 
+	if (prov->link_close) {
+		prov->link_close(BT_MESH_PROV_ADV);
+	}
+
 	/* Clear everything except the retransmit delayed work config */
 	memset(&link, 0, offsetof(struct prov_link, tx.retransmit));
 
@@ -266,7 +274,7 @@ static struct os_mbuf *adv_buf_create(void)
 
 static u8_t pending_ack = XACT_NVAL;
 
-static void ack_complete(struct os_mbuf *buf, int err)
+static void ack_complete(u16_t duration, int err, void *user_data)
 {
 	BT_DBG("xact %u complete", (u8_t)pending_ack);
 	pending_ack = XACT_NVAL;
@@ -274,7 +282,10 @@ static void ack_complete(struct os_mbuf *buf, int err)
 
 static void gen_prov_ack_send(u8_t xact_id)
 {
-	bt_mesh_adv_func_t complete;
+	static const struct bt_mesh_send_cb cb = {
+		.start = ack_complete,
+	};
+	const struct bt_mesh_send_cb *complete;
 	struct os_mbuf *buf;
 
 	BT_DBG("xact_id %u", xact_id);
@@ -291,7 +302,7 @@ static void gen_prov_ack_send(u8_t xact_id)
 
 	if (pending_ack == XACT_NVAL) {
 		pending_ack = xact_id;
-		complete = ack_complete;
+		complete = &cb;
 	} else {
 		complete = NULL;
 	}
@@ -300,7 +311,7 @@ static void gen_prov_ack_send(u8_t xact_id)
 	net_buf_add_u8(buf, xact_id);
 	net_buf_add_u8(buf, GPC_ACK);
 
-	bt_mesh_adv_send(buf, complete);
+	bt_mesh_adv_send(buf, complete, NULL);
 	net_buf_unref(buf);
 }
 
@@ -318,9 +329,9 @@ static void send_reliable(void)
 		}
 
 		if (i + 1 < ARRAY_SIZE(link.tx.buf) && link.tx.buf[i + 1]) {
-			bt_mesh_adv_send(buf, NULL);
+			bt_mesh_adv_send(buf, NULL, NULL);
 		} else {
-			bt_mesh_adv_send(buf, buf_sent);
+			bt_mesh_adv_send(buf, &buf_sent_cb, NULL);
 		}
 	}
 }
@@ -377,7 +388,7 @@ static int prov_send_adv(struct os_mbuf *msg)
 	u8_t seg_len, seg_id;
 	u8_t xact_id;
 
-	BT_DBG("prov_send_adv len %u: %s", msg->om_len, bt_hex(msg->om_data, msg->om_len));
+	BT_DBG("len %u: %s", msg->om_len, bt_hex(msg->om_data, msg->om_len));
 
 	prov_clear_tx();
 
@@ -440,7 +451,7 @@ static int prov_send_adv(struct os_mbuf *msg)
 static int prov_send_gatt(struct os_mbuf *msg)
 {
 	if (!link.conn_handle) {
-	    BT_ERR("No connection handle!?");
+		BT_ERR("No connection handle!?");
 		return -ENOTCONN;
 	}
 
@@ -470,12 +481,12 @@ static void prov_buf_init(struct os_mbuf *buf, u8_t type)
 
 static void prov_send_fail_msg(u8_t err)
 {
-    struct os_mbuf *buf = PROV_BUF(2);
+	struct os_mbuf *buf = PROV_BUF(2);
 
-    prov_buf_init(buf, PROV_FAILED);
-    net_buf_simple_add_u8(buf, err);
-    prov_send(buf);
-    os_mbuf_free_chain(buf);
+	prov_buf_init(buf, PROV_FAILED);
+	net_buf_simple_add_u8(buf, err);
+	prov_send(buf);
+	os_mbuf_free_chain(buf);
 }
 
 static void prov_invite(const u8_t *data)
@@ -553,7 +564,7 @@ static void prov_capabilities(const u8_t *data)
 	BT_INFO("Input OOB Action:  0x%04x", input_action);
 }
 
-static bt_mesh_output_action output_action(u8_t action)
+static bt_mesh_output_action_t output_action(u8_t action)
 {
 	switch (action) {
 	case OUTPUT_OOB_BLINK:
@@ -571,7 +582,7 @@ static bt_mesh_output_action output_action(u8_t action)
 	}
 }
 
-static bt_mesh_input_action input_action(u8_t action)
+static bt_mesh_input_action_t input_action(u8_t action)
 {
 	switch (action) {
 	case INPUT_OOB_PUSH:
@@ -589,8 +600,8 @@ static bt_mesh_input_action input_action(u8_t action)
 
 static int prov_auth(u8_t method, u8_t action, u8_t size)
 {
-	bt_mesh_output_action output;
-	bt_mesh_input_action input;
+	bt_mesh_output_action_t output;
+	bt_mesh_input_action_t input;
 
 	switch (method) {
 	case AUTH_METHOD_NO_OOB:
@@ -605,7 +616,9 @@ static int prov_auth(u8_t method, u8_t action, u8_t size)
 			return -EINVAL;
 		}
 
-		memcpy(link.auth, prov->static_val, prov->static_val_len);
+		memcpy(link.auth + 16 - prov->static_val_len,
+		       prov->static_val, prov->static_val_len);
+		memset(link.auth, 0, sizeof(link.auth) - prov->static_val_len);
 		return 0;
 
 	case AUTH_METHOD_OUTPUT:
@@ -623,7 +636,7 @@ static int prov_auth(u8_t method, u8_t action, u8_t size)
 		}
 
 		if (output == BT_MESH_DISPLAY_STRING) {
-			char str[9];
+			unsigned char str[9];
 			u8_t i;
 
 			bt_rand(str, size);
@@ -632,7 +645,7 @@ static int prov_auth(u8_t method, u8_t action, u8_t size)
 			for (i = 0; i < size; i++) {
 				str[i] %= 36;
 				if (str[i] < 10) {
-					str[i] += '\0';
+					str[i] += '0';
 				} else {
 					str[i] += 'A' - 10;
 				}
@@ -640,6 +653,7 @@ static int prov_auth(u8_t method, u8_t action, u8_t size)
 			str[size] = '\0';
 
 			memcpy(link.auth, str, size);
+			memset(link.auth + size, 0, sizeof(link.auth) - size);
 
 			return prov->output_string((char *)str);
 		} else {
@@ -651,6 +665,7 @@ static int prov_auth(u8_t method, u8_t action, u8_t size)
 			num %= div[size - 1];
 
 			sys_put_be32(num, &link.auth[12]);
+			memset(link.auth, 0, 12);
 
 			return prov->output_number(output, num);
 		}
@@ -709,7 +724,8 @@ static void prov_start(const u8_t *data)
 
 	if (prov_auth(data[2], data[3], data[4]) < 0) {
 		BT_ERR("Invalid authentication method: 0x%02x; "
-		       "action: 0x%02x; size: 0x%02x", data[2], data[3], data[4]);
+			"action: 0x%02x; size: 0x%02x", data[2], data[3],
+			data[4]);
 		prov_send_fail_msg(PROV_ERR_NVAL_FMT);
 	}
 }
@@ -767,18 +783,30 @@ static void send_confirm(void)
 	os_mbuf_free_chain(cfm);
 }
 
+static void send_input_complete(void)
+{
+	struct os_mbuf *buf = PROV_BUF(1);
+
+	prov_buf_init(buf, PROV_INPUT_COMPLETE);
+	prov_send(buf);
+}
+
 int bt_mesh_input_number(u32_t num)
 {
+	BT_DBG("%u", num);
+
 	if (!atomic_test_and_clear_bit(link.flags, WAIT_NUMBER)) {
 		return -EINVAL;
 	}
 
+	sys_put_be32(num, &link.auth[12]);
+
+	send_input_complete();
+
 	if (!atomic_test_bit(link.flags, HAVE_DHKEY)) {
 		return 0;
 	}
 
-	sys_put_be32(num, &link.auth[12]);
-
 	if (atomic_test_and_clear_bit(link.flags, SEND_CONFIRM)) {
 		send_confirm();
 	}
@@ -788,16 +816,20 @@ int bt_mesh_input_number(u32_t num)
 
 int bt_mesh_input_string(const char *str)
 {
+	BT_DBG("%s", str);
+
 	if (!atomic_test_and_clear_bit(link.flags, WAIT_STRING)) {
 		return -EINVAL;
 	}
 
+	strncpy((char *)link.auth, str, prov->input_size);
+
+	send_input_complete();
+
 	if (!atomic_test_bit(link.flags, HAVE_DHKEY)) {
 		return 0;
 	}
 
-	strncpy((char *)link.auth, str, prov->input_size);
-
 	if (atomic_test_and_clear_bit(link.flags, SEND_CONFIRM)) {
 		send_confirm();
 	}
@@ -851,7 +883,7 @@ static void send_pub_key(void)
 
 	memcpy(&link.conf_inputs[81], &buf->om_data[1], 64);
 
-    BT_DBG("Local Public Key: %s", bt_hex(&buf->om_data[1], 64));
+	BT_DBG("Local Public Key: %s", bt_hex(&buf->om_data[1], 64));
 
 	prov_send(buf);
 
@@ -1040,12 +1072,8 @@ static void prov_data(const u8_t *data)
 
 	bt_mesh_provision(pdu, net_idx, flags, iv_index, 0, addr, dev_key);
 
-	if (prov->complete) {
-		prov->complete();
-	}
-
 done:
-    os_mbuf_free_chain(msg);
+	os_mbuf_free_chain(msg);
 }
 
 static void prov_complete(const u8_t *data)
@@ -1105,7 +1133,7 @@ static void prov_retransmit(struct os_event *work)
 {
 	int i;
 
-	BT_DBG("prov_retransmit");
+	BT_DBG("");
 
 	if (!atomic_test_bit(link.flags, LINK_ACTIVE)) {
 		BT_WARN("Link not active");
@@ -1132,9 +1160,9 @@ static void prov_retransmit(struct os_event *work)
 		BT_DBG("%u bytes: %s", buf->om_len, bt_hex(buf->om_data, buf->om_len));
 
 		if (i + 1 < ARRAY_SIZE(link.tx.buf) && link.tx.buf[i + 1]) {
-			bt_mesh_adv_send(buf, NULL);
+			bt_mesh_adv_send(buf, NULL, NULL);
 		} else {
-			bt_mesh_adv_send(buf, buf_sent);
+			bt_mesh_adv_send(buf, &buf_sent_cb, NULL);
 		}
 
 	}
@@ -1159,6 +1187,10 @@ static void link_open(struct prov_rx *rx, struct os_mbuf *buf)
 		return;
 	}
 
+	if (prov->link_open) {
+		prov->link_open(BT_MESH_PROV_ADV);
+	}
+
 	link.id = rx->link_id;
 	atomic_set_bit(link.flags, LINK_ACTIVE);
 	net_buf_simple_init(link.rx.buf, 0);
@@ -1379,7 +1411,7 @@ static void gen_prov_recv(struct prov_rx *rx, struct os_mbuf *buf)
 		return;
 	}
 
-    BT_INFO("prov_action: %d", GPCF(rx->gpc));
+	BT_INFO("prov_action: %d", GPCF(rx->gpc));
 	gen_prov[GPCF(rx->gpc)].func(rx, buf);
 }
 
@@ -1462,6 +1494,10 @@ int bt_mesh_pb_gatt_open(uint16_t conn_handle)
 	link.conn_handle = conn_handle;
 	link.expect = PROV_INVITE;
 
+	if (prov->link_open) {
+		prov->link_open(BT_MESH_PROV_GATT);
+	}
+
 	return 0;
 }
 
@@ -1481,6 +1517,12 @@ int bt_mesh_pb_gatt_close(uint16_t conn_handle)
 		bt_mesh_attention(NULL, 0);
 	}
 
+	if (prov->link_close) {
+		prov->link_close(BT_MESH_PROV_GATT);
+	}
+
+//	bt_conn_unref(conn_handle);
+
 	pub_key = atomic_test_bit(link.flags, LOCAL_PUB_KEY);
 	memset(&link, 0, sizeof(link));
 
@@ -1504,15 +1546,21 @@ bool bt_prov_active(void)
 
 int bt_mesh_prov_init(const struct bt_mesh_prov *prov_info)
 {
-	int err;
 	static struct bt_pub_key_cb pub_key_cb = {
 		.func = pub_key_ready,
 	};
+	int err;
+
 #if !(MYNEWT_VAL(BLE_MESH_PB_GATT))
 	rx_buf = NET_BUF_SIMPLE(65);
 
 #endif
 
+	if (!prov_info) {
+		BT_ERR("No provisioning context provided");
+		return -EINVAL;
+	}
+
 	err = bt_pub_key_gen(&pub_key_cb);
 	if (err) {
 		BT_ERR("Failed to generate public key (%d)", err);
@@ -1545,4 +1593,19 @@ void bt_mesh_prov_reset_link(void) {
     link.rx.buf = rx_buf;
 #endif
 }
+
+void bt_mesh_prov_complete(u16_t net_idx, u16_t addr)
+{
+	if (prov->complete) {
+		prov->complete(net_idx, addr);
+	}
+}
+
+void bt_mesh_prov_reset(void)
+{
+	if (prov->reset) {
+		prov->reset();
+	}
+}
+
 #endif //MYNEWT_VAL(BLE_MESH_PROV) == 1
diff --git a/net/nimble/host/mesh/src/prov.h b/net/nimble/host/mesh/src/prov.h
index 1ac4757a5..3270b069d 100644
--- a/net/nimble/host/mesh/src/prov.h
+++ b/net/nimble/host/mesh/src/prov.h
@@ -13,26 +13,21 @@
 #include "mesh/mesh.h"
 #include "../src/ble_hs_conn_priv.h"
 
-void
-bt_mesh_pb_adv_recv(struct os_mbuf *buf);
+void bt_mesh_pb_adv_recv(struct os_mbuf *buf);
 
-bool
-bt_prov_active(void);
+bool bt_prov_active(void);
 
-int
-bt_mesh_pb_gatt_open(uint16_t conn_handle);
-int
-bt_mesh_pb_gatt_close(uint16_t conn_handle);
-int
-bt_mesh_pb_gatt_recv(uint16_t conn_handle, struct os_mbuf *buf);
+int bt_mesh_pb_gatt_open(uint16_t conn_handle);
+int bt_mesh_pb_gatt_close(uint16_t conn_handle);
+int bt_mesh_pb_gatt_recv(uint16_t conn_handle, struct os_mbuf *buf);
 
-const u8_t *
-bt_mesh_prov_get_uuid(void);
+const u8_t *bt_mesh_prov_get_uuid(void);
 
-int
-bt_mesh_prov_init(const struct bt_mesh_prov *prov);
+int bt_mesh_prov_init(const struct bt_mesh_prov *prov);
 
-void
-bt_mesh_prov_reset_link(void);
+void bt_mesh_prov_reset_link(void);
+
+void bt_mesh_prov_complete(u16_t net_idx, u16_t addr);
+void bt_mesh_prov_reset(void);
 
 #endif
diff --git a/net/nimble/host/mesh/src/proxy.c b/net/nimble/host/mesh/src/proxy.c
index c4e6d8929..dae72e971 100644
--- a/net/nimble/host/mesh/src/proxy.c
+++ b/net/nimble/host/mesh/src/proxy.c
@@ -81,25 +81,27 @@ ble_uuid16_t BT_UUID_MESH_PROXY_DATA_OUT       = BLE_UUID16_INIT(0x2ade);
 #define CLIENT_BUF_SIZE 68
 
 static const struct ble_gap_adv_params slow_adv_param = {
-    .conn_mode = (BLE_GAP_CONN_MODE_UND ),
-    .itvl_min = BT_GAP_ADV_SLOW_INT_MIN,
-    .itvl_max = BT_GAP_ADV_SLOW_INT_MAX,
+	.conn_mode = (BLE_GAP_CONN_MODE_UND ),
+	.itvl_min = BT_GAP_ADV_SLOW_INT_MIN,
+	.itvl_max = BT_GAP_ADV_SLOW_INT_MAX,
 };
 
 static const struct ble_gap_adv_params fast_adv_param = {
-    .conn_mode = (BLE_GAP_CONN_MODE_UND),
-    .itvl_min = BT_GAP_ADV_FAST_INT_MIN_2,
-    .itvl_max = BT_GAP_ADV_FAST_INT_MAX_2,
+	.conn_mode = (BLE_GAP_CONN_MODE_UND),
+	.itvl_min = BT_GAP_ADV_FAST_INT_MIN_2,
+	.itvl_max = BT_GAP_ADV_FAST_INT_MAX_2,
 };
 
-static const struct ble_gap_adv_params *proxy_adv_param = &fast_adv_param;
-
 static bool proxy_adv_enabled;
 
 #if (MYNEWT_VAL(BLE_MESH_GATT_PROXY))
 static void proxy_send_beacons(struct os_event *work);
 #endif
 
+#if (MYNEWT_VAL(BLE_MESH_PB_GATT))
+static bool prov_fast_adv;
+#endif
+
 static struct bt_mesh_proxy_client {
 	uint16_t conn_handle;
 	u16_t filter[MYNEWT_VAL(BLE_MESH_PROXY_FILTER_SIZE)];
@@ -126,49 +128,49 @@ static enum {
 } gatt_svc = MESH_GATT_NONE;
 
 static struct {
-    uint16_t proxy_h;
-    uint16_t proxy_data_out_h;
-    uint16_t prov_h;
-    uint16_t prov_data_in_h;
-    uint16_t prov_data_out_h;
+	uint16_t proxy_h;
+	uint16_t proxy_data_out_h;
+	uint16_t prov_h;
+	uint16_t prov_data_in_h;
+	uint16_t prov_data_out_h;
 } svc_handles;
 
 static void resolve_svc_handles(void)
 {
-    int rc;
-
-    /* Either all handles are already resolved, or none of them */
-    if (svc_handles.prov_data_out_h) {
-        return;
-    }
-
-    /*
-     * We assert if attribute is not found since at this stage all attributes
-     * shall be already registered and thus shall be found.
-     */
-
-    rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL),
-                            &svc_handles.proxy_h);
-    assert(rc == 0);
-
-    rc = ble_gatts_find_chr(BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL),
-                            BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_OUT_VAL),
-                            NULL, &svc_handles.proxy_data_out_h);
-    assert(rc == 0);
-
-    rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
-                            &svc_handles.prov_h);
-    assert(rc == 0);
-
-    rc = ble_gatts_find_chr(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
-                            BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_IN_VAL),
-                            NULL, &svc_handles.prov_data_in_h);
-    assert(rc == 0);
-
-    rc = ble_gatts_find_chr(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
-                            BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_OUT_VAL),
-                            NULL, &svc_handles.prov_data_out_h);
-    assert(rc == 0);
+	int rc;
+
+	/* Either all handles are already resolved, or none of them */
+	if (svc_handles.prov_data_out_h) {
+		return;
+	}
+
+	/*
+	 * We assert if attribute is not found since at this stage all attributes
+	 * shall be already registered and thus shall be found.
+	 */
+
+	rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL),
+				&svc_handles.proxy_h);
+	assert(rc == 0);
+
+	rc = ble_gatts_find_chr(BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL),
+				BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_OUT_VAL),
+				NULL, &svc_handles.proxy_data_out_h);
+	assert(rc == 0);
+
+	rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
+				&svc_handles.prov_h);
+	assert(rc == 0);
+
+	rc = ble_gatts_find_chr(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
+				BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_IN_VAL),
+				NULL, &svc_handles.prov_data_in_h);
+	assert(rc == 0);
+
+	rc = ble_gatts_find_chr(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
+				BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_OUT_VAL),
+				NULL, &svc_handles.prov_data_out_h);
+	assert(rc == 0);
 }
 
 static struct bt_mesh_proxy_client *find_client(uint16_t conn_handle)
@@ -185,6 +187,9 @@ static struct bt_mesh_proxy_client *find_client(uint16_t conn_handle)
 }
 
 #if (MYNEWT_VAL(BLE_MESH_GATT_PROXY))
+/* Next subnet in queue to be advertised */
+static int next_idx;
+
 static int proxy_segment_and_send(uint16_t conn_handle, u8_t type,
 				  struct os_mbuf *msg);
 
@@ -222,7 +227,7 @@ static void filter_add(struct bt_mesh_proxy_client *client, u16_t addr)
 {
 	int i;
 
-	BT_DBG("addr 0x%02x", addr);
+	BT_DBG("addr 0x%04x", addr);
 
 	if (addr == BT_MESH_ADDR_UNASSIGNED) {
 		return;
@@ -246,7 +251,7 @@ static void filter_remove(struct bt_mesh_proxy_client *client, u16_t addr)
 {
 	int i;
 
-	BT_DBG("addr 0x%02x", addr);
+	BT_DBG("addr 0x%04x", addr);
 
 	if (addr == BT_MESH_ADDR_UNASSIGNED) {
 		return;
@@ -361,7 +366,7 @@ static void proxy_cfg(struct bt_mesh_proxy_client *client)
 	}
 
 done:
-   os_mbuf_free_chain(buf);
+	os_mbuf_free_chain(buf);
 }
 
 static int beacon_send(uint16_t conn_handle, struct bt_mesh_subnet *sub)
@@ -397,6 +402,17 @@ void bt_mesh_proxy_beacon_send(struct bt_mesh_subnet *sub)
 {
 	int i;
 
+	if (!sub) {
+		/* NULL means we send on all subnets */
+		for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
+			if (bt_mesh.sub[i].net_idx != BT_MESH_KEY_UNUSED) {
+				bt_mesh_proxy_beacon_send(&bt_mesh.sub[i]);
+			}
+		}
+
+		return;
+	}
+
 	for (i = 0; i < ARRAY_SIZE(clients); i++) {
 		if (clients[i].conn_handle) {
 			beacon_send(clients[i].conn_handle, sub);
@@ -404,10 +420,24 @@ void bt_mesh_proxy_beacon_send(struct bt_mesh_subnet *sub)
 	}
 }
 
+void bt_mesh_proxy_identity_start(struct bt_mesh_subnet *sub)
+{
+	sub->node_id = BT_MESH_NODE_IDENTITY_RUNNING;
+	sub->node_id_start = k_uptime_get_32();
+
+	/* Prioritize the recently enabled subnet */
+	next_idx = sub - bt_mesh.sub;
+}
+
+void bt_mesh_proxy_identity_stop(struct bt_mesh_subnet *sub)
+{
+	sub->node_id = BT_MESH_NODE_IDENTITY_STOPPED;
+	sub->node_id_start = 0;
+}
+
 int bt_mesh_proxy_identity_enable(void)
 {
-	/* FIXME: Add support for multiple subnets */
-	struct bt_mesh_subnet *sub = &bt_mesh.sub[0];
+	int i, count = 0;
 
 	BT_DBG("");
 
@@ -415,20 +445,24 @@ int bt_mesh_proxy_identity_enable(void)
 		return -EAGAIN;
 	}
 
-	if (sub->net_idx == BT_MESH_KEY_UNUSED) {
-		return -ENOENT;
-	}
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
+		struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
 
-	if (sub->node_id == BT_MESH_NODE_IDENTITY_NOT_SUPPORTED) {
-		return -ENOTSUP;
-	}
+		if (sub->net_idx == BT_MESH_KEY_UNUSED) {
+			continue;
+		}
 
-	if (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING) {
-		return 0;
+		if (sub->node_id == BT_MESH_NODE_IDENTITY_NOT_SUPPORTED) {
+			continue;
+		}
+
+		bt_mesh_proxy_identity_start(sub);
+		count++;
 	}
 
-	sub->node_id = BT_MESH_NODE_IDENTITY_RUNNING;
-	bt_mesh_adv_update();
+	if (count) {
+		bt_mesh_adv_update();
+	}
 
 	return 0;
 }
@@ -467,7 +501,7 @@ static void proxy_complete_pdu(struct bt_mesh_proxy_client *client)
 }
 
 static int proxy_recv(uint16_t conn_handle, uint16_t attr_handle,
-                      struct ble_gatt_access_ctxt *ctxt, void *arg)
+		      struct ble_gatt_access_ctxt *ctxt, void *arg)
 {
 	struct bt_mesh_proxy_client *client;
 	const u8_t *data = ctxt->om->om_data;
@@ -485,7 +519,7 @@ static int proxy_recv(uint16_t conn_handle, uint16_t attr_handle,
 	}
 
 	if ((attr_handle == svc_handles.prov_data_in_h) !=
-	        (PDU_TYPE(data) == BT_MESH_PROXY_PROV)) {
+	    (PDU_TYPE(data) == BT_MESH_PROXY_PROV)) {
 		BT_WARN("Proxy PDU type doesn't match GATT service");
 		return -EINVAL;
 	}
@@ -550,6 +584,8 @@ static int proxy_recv(uint16_t conn_handle, uint16_t attr_handle,
 	return len;
 }
 
+static int conn_count;
+
 static void proxy_connected(uint16_t conn_handle)
 {
 	struct bt_mesh_proxy_client *client;
@@ -557,13 +593,15 @@ static void proxy_connected(uint16_t conn_handle)
 
 	BT_INFO("conn_handle %d", conn_handle);
 
+	conn_count++;
+
 	/* Since we use ADV_OPT_ONE_TIME */
 	proxy_adv_enabled = false;
 
-#if MYNEWT_VAL(BLE_MAX_CONNECTIONS) > 1
 	/* Try to re-enable advertising in case it's possible */
-	bt_mesh_adv_update();
-#endif
+	if (conn_count < CONFIG_BT_MAX_CONN) {
+		bt_mesh_adv_update();
+	}
 
 	for (client = NULL, i = 0; i < ARRAY_SIZE(clients); i++) {
 		if (!clients[i].conn_handle) {
@@ -589,6 +627,8 @@ static void proxy_disconnected(uint16_t conn_handle, int reason)
 
 	BT_INFO("conn_handle %d reason %d", conn_handle, reason);
 
+	conn_count--;
+
 	for (i = 0; i < ARRAY_SIZE(clients); i++) {
 		struct bt_mesh_proxy_client *client = &clients[i];
 
@@ -618,7 +658,7 @@ struct os_mbuf *bt_mesh_proxy_get_buf(void)
 #if (MYNEWT_VAL(BLE_MESH_PB_GATT))
 static void prov_ccc_write(uint16_t conn_handle)
 {
-    struct bt_mesh_proxy_client *client;
+	struct bt_mesh_proxy_client *client;
 
 	BT_DBG("conn_handle %d", conn_handle);
 
@@ -634,20 +674,20 @@ static void prov_ccc_write(uint16_t conn_handle)
 
 int bt_mesh_proxy_prov_enable(void)
 {
-    uint16_t handle;
-    int rc;
+	uint16_t handle;
+	int rc;
 	int i;
 
 	BT_DBG("");
 
-    rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL), &handle);
-    assert(rc == 0);
-    ble_gatts_svc_set_visibility(handle, 1);
-    /* FIXME: figure out end handle */
-    ble_svc_gatt_changed(svc_handles.prov_h, 0xffff);
+	rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL), &handle);
+	assert(rc == 0);
+	ble_gatts_svc_set_visibility(handle, 1);
+	/* FIXME: figure out end handle */
+	ble_svc_gatt_changed(svc_handles.prov_h, 0xffff);
 
-    gatt_svc = MESH_GATT_PROV;
-	proxy_adv_param = &fast_adv_param;
+	gatt_svc = MESH_GATT_PROV;
+	prov_fast_adv = true;
 
 	for (i = 0; i < ARRAY_SIZE(clients); i++) {
 		if (clients[i].conn_handle) {
@@ -661,19 +701,19 @@ int bt_mesh_proxy_prov_enable(void)
 
 int bt_mesh_proxy_prov_disable(void)
 {
-    uint16_t handle;
-    int rc;
+	uint16_t handle;
+	int rc;
 	int i;
 
 	BT_DBG("");
 
-    rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL), &handle);
-    assert(rc == 0);
-    ble_gatts_svc_set_visibility(handle, 0);
-    /* FIXME: figure out end handle */
-    ble_svc_gatt_changed(svc_handles.prov_h, 0xffff);
+	rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL), &handle);
+	assert(rc == 0);
+	ble_gatts_svc_set_visibility(handle, 0);
+	/* FIXME: figure out end handle */
+	ble_svc_gatt_changed(svc_handles.prov_h, 0xffff);
 
-    gatt_svc = MESH_GATT_NONE;
+	gatt_svc = MESH_GATT_NONE;
 
 	for (i = 0; i < ARRAY_SIZE(clients); i++) {
 		struct bt_mesh_proxy_client *client = &clients[i];
@@ -707,8 +747,8 @@ static void proxy_ccc_write(uint16_t conn_handle)
 
 int bt_mesh_proxy_gatt_enable(void)
 {
-    uint16_t handle;
-    int rc;
+	uint16_t handle;
+	int rc;
 	int i;
 
 	BT_DBG("");
@@ -716,8 +756,8 @@ int bt_mesh_proxy_gatt_enable(void)
 	rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL), &handle);
 	assert(rc == 0);
 	ble_gatts_svc_set_visibility(handle, 1);
-    /* FIXME: figure out end handle */
-    ble_svc_gatt_changed(svc_handles.proxy_h, 0xffff);
+	/* FIXME: figure out end handle */
+	ble_svc_gatt_changed(svc_handles.proxy_h, 0xffff);
 
 	gatt_svc = MESH_GATT_PROXY;
 
@@ -730,30 +770,42 @@ int bt_mesh_proxy_gatt_enable(void)
 	return 0;
 }
 
-int bt_mesh_proxy_gatt_disable(void)
+void bt_mesh_proxy_gatt_disconnect(void)
 {
-    uint16_t handle;
-    int rc;
+	int rc;
 	int i;
 
 	BT_DBG("");
 
-    rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL), &handle);
-    assert(rc == 0);
-    ble_gatts_svc_set_visibility(handle, 0);
-    /* FIXME: figure out end handle */
-    ble_svc_gatt_changed(svc_handles.proxy_h, 0xffff);
-
-	gatt_svc = MESH_GATT_NONE;
-
 	for (i = 0; i < ARRAY_SIZE(clients); i++) {
 		struct bt_mesh_proxy_client *client = &clients[i];
 
-		if (clients->conn_handle && (client->filter_type == WHITELIST ||
-				      client->filter_type == BLACKLIST)) {
+		if (client->conn_handle && (client->filter_type == WHITELIST ||
+					    client->filter_type == BLACKLIST)) {
 			client->filter_type = NONE;
+			rc = ble_gap_terminate(client->conn_handle,
+			                       BLE_ERR_REM_USER_CONN_TERM);
+			assert(rc == 0);
 		}
 	}
+}
+
+int bt_mesh_proxy_gatt_disable(void)
+{
+	uint16_t handle;
+	int rc;
+
+	BT_DBG("");
+
+	bt_mesh_proxy_gatt_disconnect();
+
+	rc = ble_gatts_find_svc(BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL), &handle);
+	assert(rc == 0);
+	ble_gatts_svc_set_visibility(handle, 0);
+	/* FIXME: figure out end handle */
+	ble_svc_gatt_changed(svc_handles.proxy_h, 0xffff);
+
+	gatt_svc = MESH_GATT_NONE;
 
 	return 0;
 }
@@ -849,23 +901,23 @@ bool bt_mesh_proxy_relay(struct os_mbuf *buf, u16_t dst)
 
 static int proxy_send(uint16_t conn_handle, const void *data, u16_t len)
 {
-    struct os_mbuf *om;
+	struct os_mbuf *om;
 
 	BT_DBG("%u bytes: %s", len, bt_hex(data, len));
 
 #if (MYNEWT_VAL(BLE_MESH_GATT_PROXY))
 	if (gatt_svc == MESH_GATT_PROXY) {
-	    om = ble_hs_mbuf_from_flat(data, len);
-	    assert(om);
-	    ble_gattc_notify_custom(conn_handle, svc_handles.proxy_data_out_h, om);
+		om = ble_hs_mbuf_from_flat(data, len);
+		assert(om);
+		ble_gattc_notify_custom(conn_handle, svc_handles.proxy_data_out_h, om);
 	}
 #endif
 
 #if (MYNEWT_VAL(BLE_MESH_PB_GATT))
 	if (gatt_svc == MESH_GATT_PROV) {
-        om = ble_hs_mbuf_from_flat(data, len);
-        assert(om);
-        ble_gattc_notify_custom(conn_handle, svc_handles.prov_data_out_h, om);
+		om = ble_hs_mbuf_from_flat(data, len);
+		assert(om);
+		ble_gattc_notify_custom(conn_handle, svc_handles.prov_data_out_h, om);
 	}
 #endif
 
@@ -940,7 +992,6 @@ static const struct bt_data prov_sd[] = {
 #endif /* PB_GATT */
 
 #if (MYNEWT_VAL(BLE_MESH_GATT_PROXY))
-static s64_t node_id_start;
 
 #define ID_TYPE_NET  0x00
 #define ID_TYPE_NODE 0x01
@@ -948,6 +999,8 @@ static s64_t node_id_start;
 #define NODE_ID_LEN  19
 #define NET_ID_LEN   11
 
+#define NODE_ID_TIMEOUT K_SECONDS(CONFIG_BT_MESH_NODE_ID_TIMEOUT)
+
 static u8_t proxy_svc_data[NODE_ID_LEN] = { 0x28, 0x18, };
 
 static const struct bt_data node_id_ad[] = {
@@ -987,10 +1040,10 @@ static int node_id_adv(struct bt_mesh_subnet *sub)
 
 	memcpy(proxy_svc_data + 3, tmp + 8, 8);
 
-	err = bt_le_adv_start(proxy_adv_param, node_id_ad,
+	err = bt_le_adv_start(&fast_adv_param, node_id_ad,
 			      ARRAY_SIZE(node_id_ad), NULL, 0);
 	if (err) {
-		BT_ERR("Failed to advertise using Node ID (err %d)", err);
+		BT_WARN("Failed to advertise using Node ID (err %d)", err);
 		return err;
 	}
 
@@ -1012,10 +1065,10 @@ static int net_id_adv(struct bt_mesh_subnet *sub)
 
 	memcpy(proxy_svc_data + 3, sub->keys[sub->kr_flag].net_id, 8);
 
-	err = bt_le_adv_start(proxy_adv_param,
-			      net_id_ad, ARRAY_SIZE(net_id_ad), NULL, 0);
+	err = bt_le_adv_start(&slow_adv_param, net_id_ad,
+			      ARRAY_SIZE(net_id_ad), NULL, 0);
 	if (err) {
-		BT_ERR("Failed to advertise using Network ID (err %d)", err);
+		BT_WARN("Failed to advertise using Network ID (err %d)", err);
 		return err;
 	}
 
@@ -1024,43 +1077,107 @@ static int net_id_adv(struct bt_mesh_subnet *sub)
 	return 0;
 }
 
-static s32_t gatt_proxy_advertise(void)
+static bool advertise_subnet(struct bt_mesh_subnet *sub)
+{
+	if (sub->net_idx == BT_MESH_KEY_UNUSED) {
+		return false;
+	}
+
+	return (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING ||
+		bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED);
+}
+
+static struct bt_mesh_subnet *next_sub(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
+		struct bt_mesh_subnet *sub;
+
+		sub = &bt_mesh.sub[(i + next_idx) % ARRAY_SIZE(bt_mesh.sub)];
+		if (advertise_subnet(sub)) {
+			next_idx = (next_idx + 1) % ARRAY_SIZE(bt_mesh.sub);
+			return sub;
+		}
+	}
+
+	return NULL;
+}
+
+static int sub_count(void)
+{
+	int i, count = 0;
+
+	for (i = 0; i < ARRAY_SIZE(bt_mesh.sub); i++) {
+		struct bt_mesh_subnet *sub = &bt_mesh.sub[i];
+
+		if (advertise_subnet(sub)) {
+			count++;
+		}
+	}
+
+	return count;
+}
+
+static s32_t gatt_proxy_advertise(struct bt_mesh_subnet *sub)
 {
-	/* TODO: Add support for multiple subnets */
-	struct bt_mesh_subnet *sub = &bt_mesh.sub[0];
 	s32_t remaining = K_FOREVER;
+	int subnet_count;
 
 	BT_DBG("");
 
-	if (sub->net_idx == BT_MESH_KEY_UNUSED) {
-		BT_WARN("First subnet is not valid");
+	if (conn_count == CONFIG_BT_MAX_CONN) {
+		BT_WARN("Connectable advertising deferred (max connections)");
 		return remaining;
 	}
 
-	if (node_id_start) {
-		s64_t active = k_uptime_get() - node_id_start;
+	if (!sub) {
+		BT_WARN("No subnets to advertise on");
+		return remaining;
+	}
 
-		BT_DBG("Node Id active for %d ms", active);
+	if (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING) {
+		u32_t active = k_uptime_get_32() - sub->node_id_start;
 
-		if (active < K_SECONDS(60)) {
-			remaining = K_SECONDS(60) - active;
+		if (active < NODE_ID_TIMEOUT) {
+			remaining = NODE_ID_TIMEOUT - active;
+			BT_DBG("Node ID active for %u ms, %d ms remaining",
+			       active, remaining);
+			node_id_adv(sub);
 		} else {
-			sub->node_id = BT_MESH_NODE_IDENTITY_STOPPED;
-			node_id_start = 0;
+			bt_mesh_proxy_identity_stop(sub);
+			BT_DBG("Node ID stopped");
 		}
 	}
 
-	if (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING) {
-		proxy_adv_param = &fast_adv_param;
-		if (node_id_adv(sub) == 0 && !node_id_start) {
-			node_id_start = k_uptime_get();
-			remaining = K_SECONDS(60);
+	if (sub->node_id == BT_MESH_NODE_IDENTITY_STOPPED) {
+		if (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED) {
+			net_id_adv(sub);
+		} else {
+			return gatt_proxy_advertise(next_sub());
+		}
+	}
+
+	subnet_count = sub_count();
+	BT_DBG("sub_count %u", subnet_count);
+	if (subnet_count > 1) {
+		s32_t max_timeout;
+
+		/* We use NODE_ID_TIMEOUT as a starting point since it may
+		 * be less than 60 seconds. Divide this period into at least
+		 * 6 slices, but make sure that a slice is at least one
+		 * second long (to avoid excessive rotation).
+		 */
+		max_timeout = NODE_ID_TIMEOUT / max(subnet_count, 6);
+		max_timeout = max(max_timeout, K_SECONDS(1));
+
+		if (remaining > max_timeout || remaining < 0) {
+			remaining = max_timeout;
 		}
-	} else if (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED) {
-		proxy_adv_param = &slow_adv_param;
-		net_id_adv(sub);
 	}
 
+	BT_DBG("Advertising %d ms for net_idx 0x%04x", remaining, sub->net_idx);
+
 	return remaining;
 }
 #endif /* GATT_PROXY */
@@ -1069,14 +1186,27 @@ s32_t bt_mesh_proxy_adv_start(void)
 {
 	BT_DBG("");
 
+	if (gatt_svc == MESH_GATT_NONE) {
+		return K_FOREVER;
+	}
+
 #if (MYNEWT_VAL(BLE_MESH_PB_GATT))
 	if (!bt_mesh_is_provisioned()) {
-		if (bt_le_adv_start(proxy_adv_param,
-				    prov_ad, ARRAY_SIZE(prov_ad),
+		const struct ble_gap_adv_params *param;
+
+		if (prov_fast_adv) {
+			param = &fast_adv_param;
+		} else {
+			param = &slow_adv_param;
+		}
+
+		if (bt_le_adv_start(param, prov_ad, ARRAY_SIZE(prov_ad),
 				    prov_sd, ARRAY_SIZE(prov_sd)) == 0) {
 			proxy_adv_enabled = true;
-			if (proxy_adv_param == &fast_adv_param) {
-				proxy_adv_param = &slow_adv_param;
+
+			/* Advertise 60 seconds using fast interval */
+			if (prov_fast_adv) {
+				prov_fast_adv = false;
 				return K_SECONDS(60);
 			}
 		}
@@ -1085,7 +1215,7 @@ s32_t bt_mesh_proxy_adv_start(void)
 
 #if (MYNEWT_VAL(BLE_MESH_GATT_PROXY))
 	if (bt_mesh_is_provisioned()) {
-		return gatt_proxy_advertise();
+		return gatt_proxy_advertise(next_sub());
 	}
 #endif /* GATT_PROXY */
 
@@ -1115,96 +1245,96 @@ ble_mesh_proxy_gap_event(struct ble_gap_event *event, void *arg)
 {
 //    BT_DBG("event %d", event->type);
 
-    if (event->type == BLE_GAP_EVENT_CONNECT) {
-        proxy_connected(event->connect.conn_handle);
-    } else if (event->type == BLE_GAP_EVENT_DISCONNECT) {
-        proxy_disconnected(event->disconnect.conn.conn_handle,
-                           event->disconnect.reason);
-    } else if (event->type == BLE_GAP_EVENT_SUBSCRIBE) {
-        if (event->subscribe.attr_handle == svc_handles.proxy_data_out_h) {
+	if (event->type == BLE_GAP_EVENT_CONNECT) {
+		proxy_connected(event->connect.conn_handle);
+	} else if (event->type == BLE_GAP_EVENT_DISCONNECT) {
+		proxy_disconnected(event->disconnect.conn.conn_handle,
+				   event->disconnect.reason);
+	} else if (event->type == BLE_GAP_EVENT_SUBSCRIBE) {
+		if (event->subscribe.attr_handle == svc_handles.proxy_data_out_h) {
 #if (MYNEWT_VAL(BLE_MESH_GATT_PROXY))
-            proxy_ccc_write(event->subscribe.conn_handle);
+			proxy_ccc_write(event->subscribe.conn_handle);
 #endif
-        } else if (event->subscribe.attr_handle ==
-                   svc_handles.prov_data_out_h) {
+		} else if (event->subscribe.attr_handle ==
+			   svc_handles.prov_data_out_h) {
 #if (MYNEWT_VAL(BLE_MESH_PB_GATT))
-            prov_ccc_write(event->subscribe.conn_handle);
+			prov_ccc_write(event->subscribe.conn_handle);
 #endif
-        }
-    }
+		}
+	}
 
-    return 0;
+	return 0;
 }
 
 static int
 dummy_access_cb(uint16_t conn_handle, uint16_t attr_handle,
-                struct ble_gatt_access_ctxt *ctxt, void *arg)
+		struct ble_gatt_access_ctxt *ctxt, void *arg)
 {
-    /*
-     * We should never never enter this callback - it's attached to notify-only
-     * characteristic which are notified directly from mbuf. And we can't pass
-     * NULL as access_cb because gatts will assert on init...
-     */
-    BLE_HS_DBG_ASSERT(0);
-    return 0;
+	/*
+	 * We should never never enter this callback - it's attached to notify-only
+	 * characteristic which are notified directly from mbuf. And we can't pass
+	 * NULL as access_cb because gatts will assert on init...
+	 */
+	BLE_HS_DBG_ASSERT(0);
+	return 0;
 }
 
 static const struct ble_gatt_svc_def svc_defs [] = {
-    {
-        .type = BLE_GATT_SVC_TYPE_PRIMARY,
-        .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL),
-        .characteristics = (struct ble_gatt_chr_def[]) { {
-            .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_IN_VAL),
-            .access_cb = proxy_recv,
-            .flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
-        }, {
-            .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_OUT_VAL),
-            .access_cb = dummy_access_cb,
-            .flags = BLE_GATT_CHR_F_NOTIFY,
-        }, {
-            0, /* No more characteristics in this service. */
-        } },
-    }, {
-        .type = BLE_GATT_SVC_TYPE_PRIMARY,
-        .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
-        .characteristics = (struct ble_gatt_chr_def[]) { {
-            .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_IN_VAL),
-            .access_cb = proxy_recv,
-            .flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
-        }, {
-            .uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_OUT_VAL),
-            .access_cb = dummy_access_cb,
-            .flags = BLE_GATT_CHR_F_NOTIFY,
-        }, {
-            0, /* No more characteristics in this service. */
-        } },
-    }, {
-        0, /* No more services. */
-    },
+	{
+		.type = BLE_GATT_SVC_TYPE_PRIMARY,
+		.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_VAL),
+		.characteristics = (struct ble_gatt_chr_def[]) { {
+				.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_IN_VAL),
+				.access_cb = proxy_recv,
+				.flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
+			}, {
+				.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROXY_DATA_OUT_VAL),
+				.access_cb = dummy_access_cb,
+				.flags = BLE_GATT_CHR_F_NOTIFY,
+			}, {
+				0, /* No more characteristics in this service. */
+			} },
+	}, {
+		.type = BLE_GATT_SVC_TYPE_PRIMARY,
+		.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_VAL),
+		.characteristics = (struct ble_gatt_chr_def[]) { {
+				.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_IN_VAL),
+				.access_cb = proxy_recv,
+				.flags = BLE_GATT_CHR_F_WRITE_NO_RSP,
+			}, {
+				.uuid = BLE_UUID16_DECLARE(BT_UUID_MESH_PROV_DATA_OUT_VAL),
+				.access_cb = dummy_access_cb,
+				.flags = BLE_GATT_CHR_F_NOTIFY,
+			}, {
+				0, /* No more characteristics in this service. */
+			} },
+	}, {
+		0, /* No more services. */
+	},
 };
 
 int bt_mesh_proxy_svcs_register(void)
 {
-    int rc;
+	int rc;
 
-    rc = ble_gatts_count_cfg(svc_defs);
-    assert(rc == 0);
+	rc = ble_gatts_count_cfg(svc_defs);
+	assert(rc == 0);
 
-    rc = ble_gatts_add_svcs(svc_defs);
-    assert(rc == 0);
+	rc = ble_gatts_add_svcs(svc_defs);
+	assert(rc == 0);
 
-    return 0;
+	return 0;
 }
 
 int bt_mesh_proxy_init(void)
 {
-    int i;
+	int i;
 
 	for (i = 0; i < MYNEWT_VAL(BLE_MAX_CONNECTIONS); ++i) {
 #if (MYNEWT_VAL(BLE_MESH_GATT_PROXY))
-	    k_work_init(&clients[i].send_beacons, proxy_send_beacons);
+		k_work_init(&clients[i].send_beacons, proxy_send_beacons);
 #endif
-	    clients[i].buf = NET_BUF_SIMPLE(CLIENT_BUF_SIZE);
+		clients[i].buf = NET_BUF_SIMPLE(CLIENT_BUF_SIZE);
 	}
 
 #if (MYNEWT_VAL(BLE_MESH_PB_GATT))
@@ -1213,8 +1343,8 @@ int bt_mesh_proxy_init(void)
 
 	resolve_svc_handles();
 
-    ble_gatts_svc_set_visibility(svc_handles.proxy_h, 0);
-    ble_gatts_svc_set_visibility(svc_handles.prov_h, 0);
+	ble_gatts_svc_set_visibility(svc_handles.proxy_h, 0);
+	ble_gatts_svc_set_visibility(svc_handles.prov_h, 0);
 
 	return 0;
 }
diff --git a/net/nimble/host/mesh/src/proxy.h b/net/nimble/host/mesh/src/proxy.h
index 655139147..9f19be07b 100644
--- a/net/nimble/host/mesh/src/proxy.h
+++ b/net/nimble/host/mesh/src/proxy.h
@@ -23,14 +23,18 @@ int bt_mesh_proxy_prov_disable(void);
 
 int bt_mesh_proxy_gatt_enable(void);
 int bt_mesh_proxy_gatt_disable(void);
+void bt_mesh_proxy_gatt_disconnect(void);
 
 void bt_mesh_proxy_beacon_send(struct bt_mesh_subnet *sub);
 
-struct os_mbuf * bt_mesh_proxy_get_buf(void);
+struct os_mbuf *bt_mesh_proxy_get_buf(void);
 
 s32_t bt_mesh_proxy_adv_start(void);
 void bt_mesh_proxy_adv_stop(void);
 
+void bt_mesh_proxy_identity_start(struct bt_mesh_subnet *sub);
+void bt_mesh_proxy_identity_stop(struct bt_mesh_subnet *sub);
+
 bool bt_mesh_proxy_relay(struct os_mbuf *buf, u16_t dst);
 void bt_mesh_proxy_addr_add(struct os_mbuf *buf, u16_t addr);
 
diff --git a/net/nimble/host/mesh/src/shell.c b/net/nimble/host/mesh/src/shell.c
new file mode 100644
index 000000000..3b668d299
--- /dev/null
+++ b/net/nimble/host/mesh/src/shell.c
@@ -0,0 +1,2118 @@
+/** @file
+ *  @brief Bluetooth Mesh shell
+ *
+ */
+
+/*
+ * Copyright (c) 2017 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "syscfg/syscfg.h"
+
+#if MYNEWT_VAL(BLE_MESH_SHELL)
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <errno.h>
+#include "shell/shell.h"
+#include "console/console.h"
+#include "mesh/mesh.h"
+#include "mesh/main.h"
+#include "mesh/glue.h"
+
+/* Private includes for raw Network & Transport layer access */
+#include "net.h"
+#include "mesh_priv.h"
+#include "transport.h"
+#include "foundation.h"
+
+#define CID_NVAL   0xffff
+#define CID_LOCAL  0x0002
+
+/* Default net, app & dev key values, unless otherwise specified */
+static const u8_t default_key[16] = {
+	0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+	0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+};
+
+static struct {
+	u16_t local;
+	u16_t dst;
+	u16_t net_idx;
+	u16_t app_idx;
+} net = {
+	.local = BT_MESH_ADDR_UNASSIGNED,
+	.dst = BT_MESH_ADDR_UNASSIGNED,
+};
+
+static struct bt_mesh_cfg_srv cfg_srv = {
+	.relay = BT_MESH_RELAY_DISABLED,
+	.beacon = BT_MESH_BEACON_DISABLED,
+#if MYNEWT_VAL(BLE_MESH_FRIEND)
+	.frnd = BT_MESH_FRIEND_DISABLED,
+#else
+	.frnd = BT_MESH_FRIEND_NOT_SUPPORTED,
+#endif
+#if MYNEWT_VAL(BLE_MESH_GATT_PROXY)
+	.gatt_proxy = BT_MESH_GATT_PROXY_DISABLED,
+#else
+	.gatt_proxy = BT_MESH_GATT_PROXY_NOT_SUPPORTED,
+#endif
+
+	.default_ttl = 7,
+
+	/* 3 transmissions with 20ms interval */
+	.net_transmit = BT_MESH_TRANSMIT(2, 20),
+	.relay_retransmit = BT_MESH_TRANSMIT(2, 20),
+};
+
+#define CUR_FAULTS_MAX 4
+
+static u8_t cur_faults[CUR_FAULTS_MAX];
+static u8_t reg_faults[CUR_FAULTS_MAX * 2];
+
+static void get_faults(u8_t *faults, u8_t faults_size, u8_t *dst, u8_t *count)
+{
+	u8_t i, limit = *count;
+
+	for (i = 0, *count = 0; i < faults_size && *count < limit; i++) {
+		if (faults[i]) {
+			*dst++ = faults[i];
+			(*count)++;
+		}
+	}
+}
+
+static int fault_get_cur(struct bt_mesh_model *model, u8_t *test_id,
+			 u16_t *company_id, u8_t *faults, u8_t *fault_count)
+{
+	printk("Sending current faults\n");
+
+	*test_id = 0x00;
+	*company_id = CID_LOCAL;
+
+	get_faults(cur_faults, sizeof(cur_faults), faults, fault_count);
+
+	return 0;
+}
+
+static int fault_get_reg(struct bt_mesh_model *model, u16_t cid,
+			 u8_t *test_id, u8_t *faults, u8_t *fault_count)
+{
+	if (cid != CID_LOCAL) {
+		printk("Faults requested for unknown Company ID 0x%04x\n", cid);
+		return -EINVAL;
+	}
+
+	printk("Sending registered faults\n");
+
+	*test_id = 0x00;
+
+	get_faults(reg_faults, sizeof(reg_faults), faults, fault_count);
+
+	return 0;
+}
+
+static int fault_clear(struct bt_mesh_model *model, uint16_t cid)
+{
+	if (cid != CID_LOCAL) {
+		return -EINVAL;
+	}
+
+	memset(reg_faults, 0, sizeof(reg_faults));
+
+	return 0;
+}
+
+static int fault_test(struct bt_mesh_model *model, uint8_t test_id,
+		      uint16_t cid)
+{
+	if (cid != CID_LOCAL) {
+		return -EINVAL;
+	}
+
+	if (test_id != 0x00) {
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static const struct bt_mesh_health_srv_cb health_srv_cb = {
+	.fault_get_cur = fault_get_cur,
+	.fault_get_reg = fault_get_reg,
+	.fault_clear = fault_clear,
+	.fault_test = fault_test,
+};
+
+static struct bt_mesh_health_srv health_srv = {
+	.cb = &health_srv_cb,
+};
+
+static struct bt_mesh_model_pub health_pub;
+
+static void
+health_pub_init(void)
+{
+    health_pub.msg  = BT_MESH_HEALTH_FAULT_MSG(CUR_FAULTS_MAX);
+}
+
+
+static struct bt_mesh_cfg_cli cfg_cli = {
+};
+
+void show_faults(u8_t test_id, u16_t cid, u8_t *faults, size_t fault_count)
+{
+	size_t i;
+
+	if (!fault_count) {
+		printk("Health Test ID 0x%02x Company ID 0x%04x: no faults\n",
+		       test_id, cid);
+		return;
+	}
+
+	printk("Health Test ID 0x%02x Company ID 0x%04x Fault Count %zu:\n",
+	       test_id, cid, fault_count);
+
+	for (i = 0; i < fault_count; i++) {
+		printk("\t0x%02x\n", faults[i]);
+	}
+}
+
+static void health_current_status(struct bt_mesh_health_cli *cli, u16_t addr,
+				  u8_t test_id, u16_t cid, u8_t *faults,
+				  size_t fault_count)
+{
+	printk("Health Current Status from 0x%04x\n", addr);
+	show_faults(test_id, cid, faults, fault_count);
+}
+
+static struct bt_mesh_health_cli health_cli = {
+	.current_status = health_current_status,
+};
+
+static u8_t dev_uuid[16] = { 0xdd, 0xdd };
+
+static struct bt_mesh_model root_models[] = {
+	BT_MESH_MODEL_CFG_SRV(&cfg_srv),
+	BT_MESH_MODEL_CFG_CLI(&cfg_cli),
+	BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
+	BT_MESH_MODEL_HEALTH_CLI(&health_cli),
+};
+
+static struct bt_mesh_elem elements[] = {
+	BT_MESH_ELEM(0, root_models, BT_MESH_MODEL_NONE),
+};
+
+static const struct bt_mesh_comp comp = {
+	.cid = CID_LOCAL,
+	.elem = elements,
+	.elem_count = ARRAY_SIZE(elements),
+};
+
+static u8_t hex2val(char c)
+{
+	if (c >= '0' && c <= '9') {
+		return c - '0';
+	} else if (c >= 'a' && c <= 'f') {
+		return c - 'a' + 10;
+	} else if (c >= 'A' && c <= 'F') {
+		return c - 'A' + 10;
+	} else {
+		return 0;
+	}
+}
+
+static size_t hex2bin(const char *hex, u8_t *bin, size_t bin_len)
+{
+	size_t len = 0;
+
+	while (*hex && len < bin_len) {
+		bin[len] = hex2val(*hex++) << 4;
+
+		if (!*hex) {
+			len++;
+			break;
+		}
+
+		bin[len++] |= hex2val(*hex++);
+	}
+
+	return len;
+}
+
+static void prov_complete(u16_t net_idx, u16_t addr)
+{
+	printk("Local node provisioned, net_idx 0x%04x address 0x%04x\n",
+	       net_idx, addr);
+	net.net_idx = net_idx,
+	net.local = addr;
+	net.dst = addr;
+}
+
+static void prov_reset(void)
+{
+	printk("The local node has been reset and needs reprovisioning\n");
+}
+
+static int output_number(bt_mesh_output_action_t action, uint32_t number)
+{
+	printk("OOB Number: %lu\n", number);
+	return 0;
+}
+
+static int output_string(const char *str)
+{
+	printk("OOB String: %s\n", str);
+	return 0;
+}
+
+static bt_mesh_input_action_t input_act;
+static u8_t input_size;
+
+static int cmd_input_num(int argc, char *argv[])
+{
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	if (input_act != BT_MESH_ENTER_NUMBER) {
+		printk("A number hasn't been requested!\n");
+		return 0;
+	}
+
+	if (strlen(argv[1]) < input_size) {
+		printk("Too short input (%u digits required)\n",
+		       input_size);
+		return 0;
+	}
+
+	err = bt_mesh_input_number(strtoul(argv[1], NULL, 10));
+	if (err) {
+		printk("Numeric input failed (err %d)\n", err);
+		return 0;
+	}
+
+	input_act = BT_MESH_NO_INPUT;
+	return 0;
+}
+
+struct shell_cmd_help cmd_input_num_help = {
+	NULL, "<number>", NULL
+};
+
+static int cmd_input_str(int argc, char *argv[])
+{
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	if (input_act != BT_MESH_ENTER_STRING) {
+		printk("A string hasn't been requested!\n");
+		return 0;
+	}
+
+	if (strlen(argv[1]) < input_size) {
+		printk("Too short input (%u characters required)\n",
+		       input_size);
+		return 0;
+	}
+
+	err = bt_mesh_input_string(argv[1]);
+	if (err) {
+		printk("String input failed (err %d)\n", err);
+		return 0;
+	}
+
+	input_act = BT_MESH_NO_INPUT;
+	return 0;
+}
+
+struct shell_cmd_help cmd_input_str_help = {
+	NULL, "<string>", NULL
+};
+
+static int input(bt_mesh_input_action_t act, u8_t size)
+{
+	switch (act) {
+	case BT_MESH_ENTER_NUMBER:
+		printk("Enter a number (max %u digits) with: input-num <num>\n",
+		       size);
+		break;
+	case BT_MESH_ENTER_STRING:
+		printk("Enter a string (max %u chars) with: input-str <str>\n",
+		       size);
+		break;
+	default:
+		printk("Unknown input action %u (size %u) requested!\n",
+		       act, size);
+		return -EINVAL;
+	}
+
+	input_act = act;
+	input_size = size;
+	return 0;
+}
+
+static const char *bearer2str(bt_mesh_prov_bearer_t bearer)
+{
+	switch (bearer) {
+	case BT_MESH_PROV_ADV:
+		return "PB-ADV";
+	case BT_MESH_PROV_GATT:
+		return "PB-GATT";
+	default:
+		return "unknown";
+	}
+}
+
+static void link_open(bt_mesh_prov_bearer_t bearer)
+{
+	printk("Provisioning link opened on %s\n", bearer2str(bearer));
+}
+
+static void link_close(bt_mesh_prov_bearer_t bearer)
+{
+	printk("Provisioning link closed on %s\n", bearer2str(bearer));
+}
+
+static u8_t static_val[16];
+
+static struct bt_mesh_prov prov = {
+	.uuid = dev_uuid,
+	.link_open = link_open,
+	.link_close = link_close,
+	.complete = prov_complete,
+	.reset = prov_reset,
+	.static_val = NULL,
+	.static_val_len = 0,
+	.output_size = 6,
+	.output_actions = (BT_MESH_DISPLAY_NUMBER | BT_MESH_DISPLAY_STRING),
+	.output_number = output_number,
+	.output_string = output_string,
+	.input_size = 6,
+	.input_actions = (BT_MESH_ENTER_NUMBER | BT_MESH_ENTER_STRING),
+	.input = input,
+};
+
+static int cmd_static_oob(int argc, char *argv[])
+{
+	if (argc < 2) {
+		prov.static_val = NULL;
+		prov.static_val_len = 0;
+	} else {
+		prov.static_val_len = hex2bin(argv[1], static_val, 16);
+		if (prov.static_val_len) {
+			prov.static_val = static_val;
+		} else {
+			prov.static_val = NULL;
+		}
+	}
+
+	if (prov.static_val) {
+		printk("Static OOB value set (length %u)\n",
+		       prov.static_val_len);
+	} else {
+		printk("Static OOB value cleared\n");
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_static_oob_help = {
+	NULL, "[val: 1-16 hex values]", NULL
+};
+
+static int cmd_uuid(int argc, char *argv[])
+{
+	u8_t uuid[16];
+	size_t len;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	len = hex2bin(argv[1], uuid, sizeof(uuid));
+	if (len < 1) {
+		return -EINVAL;
+	}
+
+	memcpy(dev_uuid, uuid, len);
+	memset(dev_uuid + len, 0, sizeof(dev_uuid) - len);
+
+	printk("Device UUID set\n");
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_uuid_help = {
+	NULL, "<UUID: 1-16 hex values>", NULL
+};
+
+static int cmd_reset(int argc, char *argv[])
+{
+	bt_mesh_reset();
+	printk("Local node reset complete\n");
+	return 0;
+}
+
+static u8_t str2u8(const char *str)
+{
+	if (isdigit(str[0])) {
+		return strtoul(str, NULL, 0);
+	}
+
+	return (!strcmp(str, "on") || !strcmp(str, "enable"));
+}
+
+static bool str2bool(const char *str)
+{
+	return str2u8(str);
+}
+
+#if MYNEWT_VAL(BLE_MESH_LOW_POWER)
+static int cmd_lpn(int argc, char *argv[])
+{
+	static bool enabled;
+	int err;
+
+	if (argc < 2) {
+		printk("%s\n", enabled ? "enabled" : "disabled");
+		return 0;
+	}
+
+	if (str2bool(argv[1])) {
+		if (enabled) {
+			printk("LPN already enabled\n");
+			return 0;
+		}
+
+		err = bt_mesh_lpn_set(true);
+		if (err) {
+			printk("Enabling LPN failed (err %d)\n", err);
+		} else {
+			enabled = true;
+		}
+	} else {
+		if (!enabled) {
+			printk("LPN already disabled\n");
+			return 0;
+		}
+
+		err = bt_mesh_lpn_set(false);
+		if (err) {
+			printk("Enabling LPN failed (err %d)\n", err);
+		} else {
+			enabled = false;
+		}
+	}
+
+	return 0;
+}
+
+static int cmd_poll(int argc, char *argv[])
+{
+	int err;
+
+	err = bt_mesh_lpn_poll();
+	if (err) {
+		printk("Friend Poll failed (err %d)\n", err);
+	}
+
+	return 0;
+}
+
+static void lpn_cb(u16_t friend_addr, bool established)
+{
+	if (established) {
+		printk("Friendship (as LPN) established to Friend 0x%04x\n",
+		       friend_addr);
+	} else {
+		printk("Friendship (as LPN) lost with Friend 0x%04x\n",
+		       friend_addr);
+	}
+}
+
+struct shell_cmd_help cmd_lpn_help = {
+	NULL, "<value: off, on>", NULL
+};
+
+#endif /* MESH_LOW_POWER */
+
+static int cmd_init(int argc, char *argv[])
+{
+	int err;
+	ble_addr_t addr;
+
+	/* Use NRPA */
+	err = ble_hs_id_gen_rnd(1, &addr);
+	assert(err == 0);
+	err = ble_hs_id_set_rnd(addr.val);
+	assert(err == 0);
+
+	err = bt_mesh_init(addr.type, &prov, &comp);
+	if (err) {
+		printk("Mesh initialization failed (err %d)\n", err);
+	}
+
+	printk("Mesh initialized\n");
+	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);
+#endif
+
+	return 0;
+}
+
+#if MYNEWT_VAL(BLE_MESH_GATT_PROXY)
+static int cmd_ident(int argc, char *argv[])
+{
+	int err;
+
+	err = bt_mesh_proxy_identity_enable();
+	if (err) {
+		printk("Failed advertise using Node Identity (err %d)\n", err);
+	}
+
+	return 0;
+}
+#endif /* MESH_GATT_PROXY */
+
+static int cmd_get_comp(int argc, char *argv[])
+{
+	struct os_mbuf*comp = NET_BUF_SIMPLE(32);
+	u8_t status, page = 0x00;
+	int err;
+
+	if (argc > 1) {
+		page = strtol(argv[1], NULL, 0);
+	}
+
+	net_buf_simple_init(comp, 0);
+	err = bt_mesh_cfg_comp_data_get(net.net_idx, net.dst, page,
+					&status, comp);
+	if (err) {
+		printk("Getting composition failed (err %d)\n", err);
+		return 0;
+	}
+
+	if (status != 0x00) {
+		printk("Got non-success status 0x%02x\n", status);
+		return 0;
+	}
+
+	printk("Got Composition Data for 0x%04x:\n", net.dst);
+	printk("\tCID      0x%04x\n", net_buf_simple_pull_le16(comp));
+	printk("\tPID      0x%04x\n", net_buf_simple_pull_le16(comp));
+	printk("\tVID      0x%04x\n", net_buf_simple_pull_le16(comp));
+	printk("\tCRPL     0x%04x\n", net_buf_simple_pull_le16(comp));
+	printk("\tFeatures 0x%04x\n", net_buf_simple_pull_le16(comp));
+
+	while (comp->om_len > 4) {
+		u8_t sig, vnd;
+		u16_t loc;
+		int i;
+
+		loc = net_buf_simple_pull_le16(comp);
+		sig = net_buf_simple_pull_u8(comp);
+		vnd = net_buf_simple_pull_u8(comp);
+
+		printk("\n\tElement @ 0x%04x:\n", loc);
+
+		if (comp->om_len < ((sig * 2) + (vnd * 4))) {
+			printk("\t\t...truncated data!\n");
+			break;
+		}
+
+		if (sig) {
+			printk("\t\tSIG Models:\n");
+		} else {
+			printk("\t\tNo SIG Models\n");
+		}
+
+		for (i = 0; i < sig; i++) {
+			u16_t mod_id = net_buf_simple_pull_le16(comp);
+
+			printk("\t\t\t0x%04x\n", mod_id);
+		}
+
+		if (vnd) {
+			printk("\t\tVendor Models:\n");
+		} else {
+			printk("\t\tNo Vendor Models\n");
+		}
+
+		for (i = 0; i < vnd; i++) {
+			u16_t cid = net_buf_simple_pull_le16(comp);
+			u16_t mod_id = net_buf_simple_pull_le16(comp);
+
+			printk("\t\t\tCompany 0x%04x: 0x%04x\n", cid, mod_id);
+		}
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_get_comp_help = {
+	NULL, "[page]", NULL
+};
+
+static int cmd_dst(int argc, char *argv[])
+{
+	if (argc < 2) {
+		printk("Destination address: 0x%04x%s\n", net.dst,
+		       net.dst == net.local ? " (local)" : "");
+		return 0;
+	}
+
+	if (!strcmp(argv[1], "local")) {
+		net.dst = net.local;
+	} else {
+		net.dst = strtoul(argv[1], NULL, 0);
+	}
+
+	printk("Destination address set to 0x%04x%s\n", net.dst,
+	       net.dst == net.local ? " (local)" : "");
+	return 0;
+}
+
+struct shell_cmd_help cmd_dst_help = {
+	NULL, "[destination address]", NULL
+};
+
+static int cmd_netidx(int argc, char *argv[])
+{
+	if (argc < 2) {
+		printk("NetIdx: 0x%04x\n", net.net_idx);
+		return 0;
+	}
+
+	net.net_idx = strtoul(argv[1], NULL, 0);
+	printk("NetIdx set to 0x%04x\n", net.net_idx);
+	return 0;
+}
+
+struct shell_cmd_help cmd_netidx_help = {
+	NULL, "[NetIdx]", NULL
+};
+
+static int cmd_appidx(int argc, char *argv[])
+{
+	if (argc < 2) {
+		printk("AppIdx: 0x%04x\n", net.app_idx);
+		return 0;
+	}
+
+	net.app_idx = strtoul(argv[1], NULL, 0);
+	printk("AppIdx set to 0x%04x\n", net.app_idx);
+	return 0;
+}
+
+struct shell_cmd_help cmd_appidx_help = {
+	NULL, "[AppIdx]", NULL
+};
+
+static int cmd_net_send(int argc, char *argv[])
+{
+	struct os_mbuf *msg = NET_BUF_SIMPLE(32);
+	struct bt_mesh_msg_ctx ctx = {
+		.send_ttl = BT_MESH_TTL_DEFAULT,
+		.net_idx = net.net_idx,
+		.addr = net.dst,
+		.app_idx = net.app_idx,
+
+	};
+	struct bt_mesh_net_tx tx = {
+		.ctx = &ctx,
+		.src = net.local,
+		.xmit = bt_mesh_net_transmit_get(),
+		.sub = bt_mesh_subnet_get(net.net_idx),
+	};
+	size_t len;
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	if (!tx.sub) {
+		printk("No matching subnet for NetKey Index 0x%04x\n",
+		       net.net_idx);
+		return 0;
+	}
+
+	net_buf_simple_init(msg, 0);
+	len = hex2bin(argv[1], msg->om_data, net_buf_simple_tailroom(msg) - 4);
+	net_buf_simple_add(msg, len);
+
+	err = bt_mesh_trans_send(&tx, msg, NULL, NULL);
+	if (err) {
+		printk("Failed to send (err %d)\n", err);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_net_send_help = {
+	NULL, "<hex string>", NULL
+};
+
+static int cmd_iv_update(int argc, char *argv[])
+{
+	if (bt_mesh_iv_update()) {
+		printk("Transitioned to IV Update In Progress state\n");
+	} else {
+		printk("Transitioned to IV Update Normal state\n");
+	}
+
+	printk("IV Index is 0x%08lx\n", bt_mesh.iv_index);
+
+	return 0;
+}
+
+static int cmd_rpl_clear(int argc, char *argv[])
+{
+	bt_mesh_rpl_clear();
+	return 0;
+}
+
+static int cmd_iv_update_test(int argc, char *argv[])
+{
+	bool enable;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	enable = str2bool(argv[1]);
+	if (enable) {
+		printk("Enabling IV Update test mode\n");
+	} else {
+		printk("Disabling IV Update test mode\n");
+	}
+
+	bt_mesh_iv_update_test(enable);
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_iv_update_test_help = {
+	NULL, "<value: off, on>", NULL
+};
+
+static int cmd_beacon(int argc, char *argv[])
+{
+	u8_t status;
+	int err;
+
+	if (argc < 2) {
+		err = bt_mesh_cfg_beacon_get(net.net_idx, net.dst, &status);
+	} else {
+		u8_t val = str2u8(argv[1]);
+
+		err = bt_mesh_cfg_beacon_set(net.net_idx, net.dst, val,
+					     &status);
+	}
+
+	if (err) {
+		printk("Unable to send Beacon Get/Set message (err %d)\n", err);
+		return 0;
+	}
+
+	printk("Beacon state is 0x%02x\n", status);
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_beacon_help = {
+	NULL, "[val: off, on]", NULL
+};
+
+static int cmd_ttl(int argc, char *argv[])
+{
+	u8_t ttl;
+	int err;
+
+	if (argc < 2) {
+		err = bt_mesh_cfg_ttl_get(net.net_idx, net.dst, &ttl);
+	} else {
+		u8_t val = strtoul(argv[1], NULL, 0);
+
+		err = bt_mesh_cfg_ttl_set(net.net_idx, net.dst, val, &ttl);
+	}
+
+	if (err) {
+		printk("Unable to send Default TTL Get/Set (err %d)\n", err);
+		return 0;
+	}
+
+	printk("Default TTL is 0x%02x\n", ttl);
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_ttl_help = {
+	NULL, "[ttl: 0x00, 0x02-0x7f]", NULL
+};
+
+static int cmd_friend(int argc, char *argv[])
+{
+	u8_t frnd;
+	int err;
+
+	if (argc < 2) {
+		err = bt_mesh_cfg_friend_get(net.net_idx, net.dst, &frnd);
+	} else {
+		u8_t val = str2u8(argv[1]);
+
+		err = bt_mesh_cfg_friend_set(net.net_idx, net.dst, val, &frnd);
+	}
+
+	if (err) {
+		printk("Unable to send Friend Get/Set (err %d)\n", err);
+		return 0;
+	}
+
+	printk("Friend is set to 0x%02x\n", frnd);
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_friend_help = {
+	NULL, "[val: off, on]", NULL
+};
+
+static int cmd_gatt_proxy(int argc, char *argv[])
+{
+	u8_t proxy;
+	int err;
+
+	if (argc < 2) {
+		err = bt_mesh_cfg_gatt_proxy_get(net.net_idx, net.dst, &proxy);
+	} else {
+		u8_t val = str2u8(argv[1]);
+
+		err = bt_mesh_cfg_gatt_proxy_set(net.net_idx, net.dst, val,
+						 &proxy);
+	}
+
+	if (err) {
+		printk("Unable to send GATT Proxy Get/Set (err %d)\n", err);
+		return 0;
+	}
+
+	printk("GATT Proxy is set to 0x%02x\n", proxy);
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_gatt_proxy_help = {
+	NULL, "[val: off, on]", NULL
+};
+
+static int cmd_relay(int argc, char *argv[])
+{
+	u8_t relay, transmit;
+	int err;
+
+	if (argc < 2) {
+		err = bt_mesh_cfg_relay_get(net.net_idx, net.dst, &relay,
+					    &transmit);
+	} else {
+		u8_t val = str2u8(argv[1]);
+		u8_t count, interval, new_transmit;
+
+		if (val) {
+			if (argc > 2) {
+				count = strtoul(argv[2], NULL, 0);
+			} else {
+				count = 2;
+			}
+
+			if (argc > 3) {
+				interval = strtoul(argv[3], NULL, 0);
+			} else {
+				interval = 20;
+			}
+
+			new_transmit = BT_MESH_TRANSMIT(count, interval);
+		} else {
+			new_transmit = 0;
+		}
+
+		err = bt_mesh_cfg_relay_set(net.net_idx, net.dst, val,
+					    new_transmit, &relay, &transmit);
+	}
+
+	if (err) {
+		printk("Unable to send Relay Get/Set (err %d)\n", err);
+		return 0;
+	}
+
+	printk("Relay is 0x%02x, Transmit 0x%02x (count %u interval %ums)\n",
+	       relay, transmit, BT_MESH_TRANSMIT_COUNT(transmit),
+	       BT_MESH_TRANSMIT_INT(transmit));
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_relay_help = {
+	NULL, "[val: off, on] [count: 0-7] [interval: 0-32]", NULL
+};
+
+static int cmd_net_key_add(int argc, char *argv[])
+{
+	u8_t key_val[16];
+	u16_t key_net_idx;
+	u8_t status;
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	key_net_idx = strtoul(argv[1], NULL, 0);
+
+	if (argc > 2) {
+		size_t len;
+
+		len = hex2bin(argv[3], key_val, sizeof(key_val));
+		memset(key_val, 0, sizeof(key_val) - len);
+	} else {
+		memcpy(key_val, default_key, sizeof(key_val));
+	}
+
+	err = bt_mesh_cfg_net_key_add(net.net_idx, net.dst, key_net_idx,
+				      key_val, &status);
+	if (err) {
+		printk("Unable to send NetKey Add (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("NetKeyAdd failed with status 0x%02x\n", status);
+	} else {
+		printk("NetKey added with NetKey Index 0x%03x\n", key_net_idx);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_net_key_add_help = {
+	NULL, "<NetKeyIndex> [val]", NULL
+};
+
+static int cmd_app_key_add(int argc, char *argv[])
+{
+	u8_t key_val[16];
+	u16_t key_net_idx, key_app_idx;
+	u8_t status;
+	int err;
+
+	if (argc < 3) {
+		return -EINVAL;
+	}
+
+	key_net_idx = strtoul(argv[1], NULL, 0);
+	key_app_idx = strtoul(argv[2], NULL, 0);
+
+	if (argc > 3) {
+		size_t len;
+
+		len = hex2bin(argv[3], key_val, sizeof(key_val));
+		memset(key_val, 0, sizeof(key_val) - len);
+	} else {
+		memcpy(key_val, default_key, sizeof(key_val));
+	}
+
+	err = bt_mesh_cfg_app_key_add(net.net_idx, net.dst, key_net_idx,
+				      key_app_idx, key_val, &status);
+	if (err) {
+		printk("Unable to send App Key Add (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("AppKeyAdd failed with status 0x%02x\n", status);
+	} else {
+		printk("AppKey added, NetKeyIndex 0x%04x AppKeyIndex 0x%04x\n",
+		       key_net_idx, key_app_idx);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_app_key_add_help = {
+	NULL, "<NetKeyIndex> <AppKeyIndex> [val]", NULL
+};
+
+static int cmd_mod_app_bind(int argc, char *argv[])
+{
+	u16_t elem_addr, mod_app_idx, mod_id, cid;
+	u8_t status;
+	int err;
+
+	if (argc < 4) {
+		return -EINVAL;
+	}
+
+	elem_addr = strtoul(argv[1], NULL, 0);
+	mod_app_idx = strtoul(argv[2], NULL, 0);
+	mod_id = strtoul(argv[3], NULL, 0);
+
+	if (argc > 4) {
+		cid = strtoul(argv[4], NULL, 0);
+		err = bt_mesh_cfg_mod_app_bind_vnd(net.net_idx, net.dst,
+						   elem_addr, mod_app_idx,
+						   mod_id, cid, &status);
+	} else {
+		err = bt_mesh_cfg_mod_app_bind(net.net_idx, net.dst, elem_addr,
+					       mod_app_idx, mod_id, &status);
+	}
+
+	if (err) {
+		printk("Unable to send Model App Bind (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Model App Bind failed with status 0x%02x\n", status);
+	} else {
+		printk("AppKey successfully bound\n");
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_mod_app_bind_help = {
+	NULL, "<addr> <AppIndex> <Model ID> [Company ID]", NULL
+};
+
+static int cmd_mod_sub_add(int argc, char *argv[])
+{
+	u16_t elem_addr, sub_addr, mod_id, cid;
+	u8_t status;
+	int err;
+
+	if (argc < 4) {
+		return -EINVAL;
+	}
+
+	elem_addr = strtoul(argv[1], NULL, 0);
+	sub_addr = strtoul(argv[2], NULL, 0);
+	mod_id = strtoul(argv[3], NULL, 0);
+
+	if (argc > 4) {
+		cid = strtoul(argv[4], NULL, 0);
+		err = bt_mesh_cfg_mod_sub_add_vnd(net.net_idx, net.dst,
+						  elem_addr, sub_addr, mod_id,
+						  cid, &status);
+	} else {
+		err = bt_mesh_cfg_mod_sub_add(net.net_idx, net.dst, elem_addr,
+					      sub_addr, mod_id, &status);
+	}
+
+	if (err) {
+		printk("Unable to send Model Subscription Add (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Model Subscription Add failed with status 0x%02x\n",
+		       status);
+	} else {
+		printk("Model subscription was successful\n");
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_mod_sub_add_help = {
+	NULL, "<elem addr> <sub addr> <Model ID> [Company ID]", NULL
+};
+
+static int cmd_mod_sub_del(int argc, char *argv[])
+{
+	u16_t elem_addr, sub_addr, mod_id, cid;
+	u8_t status;
+	int err;
+
+	if (argc < 4) {
+		return -EINVAL;
+	}
+
+	elem_addr = strtoul(argv[1], NULL, 0);
+	sub_addr = strtoul(argv[2], NULL, 0);
+	mod_id = strtoul(argv[3], NULL, 0);
+
+	if (argc > 4) {
+		cid = strtoul(argv[4], NULL, 0);
+		err = bt_mesh_cfg_mod_sub_del_vnd(net.net_idx, net.dst,
+						  elem_addr, sub_addr, mod_id,
+						  cid, &status);
+	} else {
+		err = bt_mesh_cfg_mod_sub_del(net.net_idx, net.dst, elem_addr,
+					      sub_addr, mod_id, &status);
+	}
+
+	if (err) {
+		printk("Unable to send Model Subscription Delete (err %d)\n",
+		       err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Model Subscription Delete failed with status 0x%02x\n",
+		       status);
+	} else {
+		printk("Model subscription deltion was successful\n");
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_mod_sub_del_help = {
+	NULL, "<elem addr> <sub addr> <Model ID> [Company ID]", NULL
+};
+
+static int cmd_mod_sub_add_va(int argc, char *argv[])
+{
+	u16_t elem_addr, sub_addr, mod_id, cid;
+	u8_t label[16];
+	u8_t status;
+	size_t len;
+	int err;
+
+	if (argc < 4) {
+		return -EINVAL;
+	}
+
+	elem_addr = strtoul(argv[1], NULL, 0);
+
+	len = hex2bin(argv[2], label, sizeof(label));
+	memset(label + len, 0, sizeof(label) - len);
+
+	mod_id = strtoul(argv[3], NULL, 0);
+
+	if (argc > 4) {
+		cid = strtoul(argv[4], NULL, 0);
+		err = bt_mesh_cfg_mod_sub_va_add_vnd(net.net_idx, net.dst,
+						     elem_addr, label, mod_id,
+						     cid, &sub_addr, &status);
+	} else {
+		err = bt_mesh_cfg_mod_sub_va_add(net.net_idx, net.dst,
+						 elem_addr, label, mod_id,
+						 &sub_addr, &status);
+	}
+
+	if (err) {
+		printk("Unable to send Mod Sub VA Add (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Mod Sub VA Add failed with status 0x%02x\n",
+		       status);
+	} else {
+		printk("0x%04x subscribed to Label UUID %s (va 0x%04x)\n",
+		       elem_addr, argv[2], sub_addr);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_mod_sub_add_va_help = {
+	NULL, "<elem addr> <Label UUID> <Model ID> [Company ID]", NULL
+};
+
+static int cmd_mod_sub_del_va(int argc, char *argv[])
+{
+	u16_t elem_addr, sub_addr, mod_id, cid;
+	u8_t label[16];
+	u8_t status;
+	size_t len;
+	int err;
+
+	if (argc < 4) {
+		return -EINVAL;
+	}
+
+	elem_addr = strtoul(argv[1], NULL, 0);
+
+	len = hex2bin(argv[2], label, sizeof(label));
+	memset(label + len, 0, sizeof(label) - len);
+
+	mod_id = strtoul(argv[3], NULL, 0);
+
+	if (argc > 4) {
+		cid = strtoul(argv[4], NULL, 0);
+		err = bt_mesh_cfg_mod_sub_va_del_vnd(net.net_idx, net.dst,
+						     elem_addr, label, mod_id,
+						     cid, &sub_addr, &status);
+	} else {
+		err = bt_mesh_cfg_mod_sub_va_del(net.net_idx, net.dst,
+						 elem_addr, label, mod_id,
+						 &sub_addr, &status);
+	}
+
+	if (err) {
+		printk("Unable to send Model Subscription Delete (err %d)\n",
+		       err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Model Subscription Delete failed with status 0x%02x\n",
+		       status);
+	} else {
+		printk("0x%04x unsubscribed from Label UUID %s (va 0x%04x)\n",
+		       elem_addr, argv[2], sub_addr);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_mod_sub_del_va_help = {
+	NULL, "<elem addr> <Label UUID> <Model ID> [Company ID]", NULL
+};
+
+static int mod_pub_get(u16_t addr, u16_t mod_id, u16_t cid)
+{
+	struct bt_mesh_cfg_mod_pub pub;
+	u8_t status;
+	int err;
+
+	if (cid == CID_NVAL) {
+		err = bt_mesh_cfg_mod_pub_get(net.net_idx, net.dst, addr,
+					      mod_id, &pub, &status);
+	} else {
+		err = bt_mesh_cfg_mod_pub_get_vnd(net.net_idx, net.dst, addr,
+						  mod_id, cid, &pub, &status);
+	}
+
+	if (err) {
+		printk("Model Publication Get failed (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Model Publication Get failed (status 0x%02x)\n",
+		       status);
+		return 0;
+	}
+
+	printk("Model Publication for Element 0x%04x, Model 0x%04x:\n"
+	       "\tPublish Address:                0x%04x\n"
+	       "\tAppKeyIndex:                    0x%04x\n"
+	       "\tCredential Flag:                %u\n"
+	       "\tPublishTTL:                     %u\n"
+	       "\tPublishPeriod:                  0x%02x\n"
+	       "\tPublishRetransmitCount:         %u\n"
+	       "\tPublishRetransmitInterval:      %ums\n",
+	       addr, mod_id, pub.addr, pub.app_idx, pub.cred_flag, pub.ttl,
+	       pub.period, BT_MESH_PUB_TRANSMIT_COUNT(pub.transmit),
+	       BT_MESH_PUB_TRANSMIT_INT(pub.transmit));
+
+	return 0;
+}
+
+static int mod_pub_set(u16_t addr, u16_t mod_id, u16_t cid, char *argv[])
+{
+	struct bt_mesh_cfg_mod_pub pub;
+	u8_t status, count;
+	u16_t interval;
+	int err;
+
+	pub.addr = strtoul(argv[0], NULL, 0);
+	pub.app_idx = strtoul(argv[1], NULL, 0);
+	pub.cred_flag = str2bool(argv[2]);
+	pub.ttl = strtoul(argv[3], NULL, 0);
+	pub.period = strtoul(argv[4], NULL, 0);
+
+	count = strtoul(argv[5], NULL, 0);
+	if (count > 7) {
+		printk("Invalid retransmit count\n");
+		return -EINVAL;
+	}
+
+	interval = strtoul(argv[6], NULL, 0);
+	if (interval > (31 * 50) || (interval % 50)) {
+		printk("Invalid retransmit interval %u\n", interval);
+		return -EINVAL;
+	}
+
+	pub.transmit = BT_MESH_PUB_TRANSMIT(count, interval);
+
+	if (cid == CID_NVAL) {
+		err = bt_mesh_cfg_mod_pub_set(net.net_idx, net.dst, addr,
+					      mod_id, &pub, &status);
+	} else {
+		err = bt_mesh_cfg_mod_pub_set_vnd(net.net_idx, net.dst, addr,
+						  mod_id, cid, &pub, &status);
+	}
+
+	if (err) {
+		printk("Model Publication Set failed (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Model Publication Set failed (status 0x%02x)\n",
+		       status);
+	} else {
+		printk("Model Publication successfully set\n");
+	}
+
+	return 0;
+}
+
+static int cmd_mod_pub(int argc, char *argv[])
+{
+	u16_t addr, mod_id, cid;
+
+	if (argc < 3) {
+		return -EINVAL;
+	}
+
+	addr = strtoul(argv[1], NULL, 0);
+	mod_id = strtoul(argv[2], NULL, 0);
+
+	argc -= 3;
+	argv += 3;
+
+	if (argc == 1 || argc == 8) {
+		cid = strtoul(argv[0], NULL, 0);
+		argc--;
+		argv++;
+	} else {
+		cid = CID_NVAL;
+	}
+
+	if (argc > 0) {
+		if (argc < 7) {
+			return -EINVAL;
+		}
+
+		return mod_pub_set(addr, mod_id, cid, argv);
+	} else {
+		return mod_pub_get(addr, mod_id, cid);
+	}
+}
+
+struct shell_cmd_help cmd_mod_pub_help = {
+	NULL, "<addr> <mod id> [cid] [<PubAddr> "
+	"<AppKeyIndex> <cred> <ttl> <period> <count> <interval>]" , NULL
+};
+
+static void hb_sub_print(struct bt_mesh_cfg_hb_sub *sub)
+{
+	printk("Heartbeat Subscription:\n"
+	       "\tSource:      0x%04x\n"
+	       "\tDestination: 0x%04x\n"
+	       "\tPeriodLog:   0x%02x\n"
+	       "\tCountLog:    0x%02x\n"
+	       "\tMinHops:     %u\n"
+	       "\tMaxHops:     %u\n",
+	       sub->src, sub->dst, sub->period, sub->count,
+	       sub->min, sub->max);
+}
+
+static int hb_sub_get(int argc, char *argv[])
+{
+	struct bt_mesh_cfg_hb_sub sub;
+	u8_t status;
+	int err;
+
+	err = bt_mesh_cfg_hb_sub_get(net.net_idx, net.dst, &sub, &status);
+	if (err) {
+		printk("Heartbeat Subscription Get failed (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Heartbeat Subscription Get failed (status 0x%02x)\n",
+		       status);
+	} else {
+		hb_sub_print(&sub);
+	}
+
+	return 0;
+}
+
+static int hb_sub_set(int argc, char *argv[])
+{
+	struct bt_mesh_cfg_hb_sub sub;
+	u8_t status;
+	int err;
+
+	sub.src = strtoul(argv[1], NULL, 0);
+	sub.dst = strtoul(argv[2], NULL, 0);
+	sub.period = strtoul(argv[3], NULL, 0);
+
+	err = bt_mesh_cfg_hb_sub_set(net.net_idx, net.dst, &sub, &status);
+	if (err) {
+		printk("Heartbeat Subscription Set failed (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Heartbeat Subscription Set failed (status 0x%02x)\n",
+		       status);
+	} else {
+		hb_sub_print(&sub);
+	}
+
+	return 0;
+}
+
+static int cmd_hb_sub(int argc, char *argv[])
+{
+	if (argc > 1) {
+		if (argc < 4) {
+			return -EINVAL;
+		}
+
+		return hb_sub_set(argc, argv);
+	} else {
+		return hb_sub_get(argc, argv);
+	}
+}
+
+struct shell_cmd_help cmd_hb_sub_help = {
+	NULL, "<src> <dst> <period>", NULL
+};
+
+static int hb_pub_get(int argc, char *argv[])
+{
+	struct bt_mesh_cfg_hb_pub pub;
+	u8_t status;
+	int err;
+
+	err = bt_mesh_cfg_hb_pub_get(net.net_idx, net.dst, &pub, &status);
+	if (err) {
+		printk("Heartbeat Publication Get failed (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Heartbeat Publication Get failed (status 0x%02x)\n",
+		       status);
+		return 0;
+	}
+
+	printk("Heartbeat publication:\n");
+	printk("\tdst 0x%04x count 0x%02x period 0x%02x\n",
+	       pub.dst, pub.count, pub.period);
+	printk("\tttl 0x%02x feat 0x%04x net_idx 0x%04x\n",
+	       pub.ttl, pub.feat, pub.net_idx);
+
+	return 0;
+}
+
+static int hb_pub_set(int argc, char *argv[])
+{
+	struct bt_mesh_cfg_hb_pub pub;
+	u8_t status;
+	int err;
+
+	pub.dst = strtoul(argv[1], NULL, 0);
+	pub.count = strtoul(argv[2], NULL, 0);
+	pub.period = strtoul(argv[3], NULL, 0);
+	pub.ttl = strtoul(argv[4], NULL, 0);
+	pub.feat = strtoul(argv[5], NULL, 0);
+	pub.net_idx = strtoul(argv[5], NULL, 0);
+
+	err = bt_mesh_cfg_hb_pub_set(net.net_idx, net.dst, &pub, &status);
+	if (err) {
+		printk("Heartbeat Publication Set failed (err %d)\n", err);
+		return 0;
+	}
+
+	if (status) {
+		printk("Heartbeat Publication Set failed (status 0x%02x)\n",
+		       status);
+	} else {
+		printk("Heartbeat publication successfully set\n");
+	}
+
+	return 0;
+}
+
+static int cmd_hb_pub(int argc, char *argv[])
+{
+	if (argc > 1) {
+		if (argc < 7) {
+			return -EINVAL;
+		}
+
+		return hb_pub_set(argc, argv);
+	} else {
+		return hb_pub_get(argc, argv);
+	}
+}
+
+struct shell_cmd_help cmd_hb_pub_help = {
+	NULL, "<dst> <count> <period> <ttl> <features> <NetKeyIndex>" , NULL
+};
+
+#if MYNEWT_VAL(BLE_MESH_PROV)
+static int cmd_pb(bt_mesh_prov_bearer_t bearer, int argc, char *argv[])
+{
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	if (str2bool(argv[1])) {
+		err = bt_mesh_prov_enable(bearer);
+		if (err) {
+			printk("Failed to enable %s (err %d)\n",
+			       bearer2str(bearer), err);
+		} else {
+			printk("%s enabled\n", bearer2str(bearer));
+		}
+	} else {
+		err = bt_mesh_prov_disable(bearer);
+		if (err) {
+			printk("Failed to disable %s (err %d)\n",
+			       bearer2str(bearer), err);
+		} else {
+			printk("%s disabled\n", bearer2str(bearer));
+		}
+	}
+
+	return 0;
+
+}
+
+struct shell_cmd_help cmd_pb_help = {
+	NULL, "<val: off, on>", NULL
+};
+
+#endif
+
+#if MYNEWT_VAL(BLE_MESH_PB_ADV)
+static int cmd_pb_adv(int argc, char *argv[])
+{
+	return cmd_pb(BT_MESH_PROV_ADV, argc, argv);
+}
+#endif /* CONFIG_BT_MESH_PB_ADV */
+
+#if MYNEWT_VAL(BLE_MESH_PB_GATT)
+static int cmd_pb_gatt(int argc, char *argv[])
+{
+	return cmd_pb(BT_MESH_PROV_GATT, argc, argv);
+}
+#endif /* CONFIG_BT_MESH_PB_GATT */
+
+static int cmd_provision(int argc, char *argv[])
+{
+	u16_t net_idx, addr;
+	u32_t iv_index;
+	int err;
+
+	if (argc < 3) {
+		return -EINVAL;
+	}
+
+	net_idx = strtoul(argv[1], NULL, 0);
+	addr = strtoul(argv[2], NULL, 0);
+
+	if (argc > 3) {
+		iv_index = strtoul(argv[1], NULL, 0);
+	} else {
+		iv_index = 0;
+	}
+
+	err = bt_mesh_provision(default_key, net_idx, 0, iv_index, 0, addr,
+				default_key);
+	if (err) {
+		printk("Provisioning failed (err %d)\n", err);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_provision_help = {
+	NULL, "<NetKeyIndex> <addr> [IVIndex]" , NULL
+};
+
+int cmd_timeout(int argc, char *argv[])
+{
+	s32_t timeout;
+
+	if (argc < 2) {
+		timeout = bt_mesh_cfg_cli_timeout_get();
+		if (timeout == K_FOREVER) {
+			printk("Message timeout: forever\n");
+		} else {
+			printk("Message timeout: %lu seconds\n",
+			       timeout / 1000);
+		}
+
+		return 0;
+	}
+
+	timeout = strtol(argv[1], NULL, 0);
+	if (timeout < 0 || timeout > (INT32_MAX / 1000)) {
+		timeout = K_FOREVER;
+	} else {
+		timeout = timeout * 1000;
+	}
+
+	bt_mesh_cfg_cli_timeout_set(timeout);
+	if (timeout == K_FOREVER) {
+		printk("Message timeout: forever\n");
+	} else {
+		printk("Message timeout: %lu seconds\n",
+		       timeout / 1000);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_timeout_help = {
+	NULL, "[timeout in seconds]", NULL
+};
+
+static int cmd_fault_get(int argc, char *argv[])
+{
+	u8_t faults[32];
+	size_t fault_count;
+	u8_t test_id;
+	u16_t cid;
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	cid = strtoul(argv[1], NULL, 0);
+	fault_count = sizeof(faults);
+
+	err = bt_mesh_health_fault_get(net.net_idx, net.dst, net.app_idx, cid,
+				       &test_id, faults, &fault_count);
+	if (err) {
+		printk("Failed to send Health Fault Get (err %d)\n", err);
+	} else {
+		show_faults(test_id, cid, faults, fault_count);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_fault_get_help = {
+	NULL, "<Company ID>", NULL
+};
+
+static int cmd_fault_clear(int argc, char *argv[])
+{
+	u8_t faults[32];
+	size_t fault_count;
+	u8_t test_id;
+	u16_t cid;
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	cid = strtoul(argv[1], NULL, 0);
+	fault_count = sizeof(faults);
+
+	err = bt_mesh_health_fault_clear(net.net_idx, net.dst, net.app_idx,
+					 cid, &test_id, faults, &fault_count);
+	if (err) {
+		printk("Failed to send Health Fault Clear (err %d)\n", err);
+	} else {
+		show_faults(test_id, cid, faults, fault_count);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_fault_clear_help = {
+	NULL, "<Company ID>", NULL
+};
+
+static int cmd_fault_clear_unack(int argc, char *argv[])
+{
+	u16_t cid;
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	cid = strtoul(argv[1], NULL, 0);
+
+	err = bt_mesh_health_fault_clear(net.net_idx, net.dst, net.app_idx,
+					 cid, NULL, NULL, NULL);
+	if (err) {
+		printk("Health Fault Clear Unacknowledged failed (err %d)\n",
+		       err);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_fault_clear_unack_help = {
+	NULL, "<Company ID>", NULL
+};
+
+static int cmd_fault_test(int argc, char *argv[])
+{
+	u8_t faults[32];
+	size_t fault_count;
+	u8_t test_id;
+	u16_t cid;
+	int err;
+
+	if (argc < 3) {
+		return -EINVAL;
+	}
+
+	cid = strtoul(argv[1], NULL, 0);
+	test_id = strtoul(argv[2], NULL, 0);
+	fault_count = sizeof(faults);
+
+	err = bt_mesh_health_fault_test(net.net_idx, net.dst, net.app_idx,
+					cid, test_id, faults, &fault_count);
+	if (err) {
+		printk("Failed to send Health Fault Test (err %d)\n", err);
+	} else {
+		show_faults(test_id, cid, faults, fault_count);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_fault_test_help = {
+	NULL, "<Company ID> <Test ID>", NULL
+};
+
+static int cmd_fault_test_unack(int argc, char *argv[])
+{
+	u16_t cid;
+	u8_t test_id;
+	int err;
+
+	if (argc < 3) {
+		return -EINVAL;
+	}
+
+	cid = strtoul(argv[1], NULL, 0);
+	test_id = strtoul(argv[2], NULL, 0);
+
+	err = bt_mesh_health_fault_test(net.net_idx, net.dst, net.app_idx,
+					cid, test_id, NULL, NULL);
+	if (err) {
+		printk("Health Fault Test Unacknowledged failed (err %d)\n",
+		       err);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_fault_test_unack_help = {
+	NULL, "<Company ID> <Test ID>", NULL
+};
+
+static int cmd_period_get(int argc, char *argv[])
+{
+	u8_t divisor;
+	int err;
+
+	err = bt_mesh_health_period_get(net.net_idx, net.dst, net.app_idx,
+					&divisor);
+	if (err) {
+		printk("Failed to send Health Period Get (err %d)\n", err);
+	} else {
+		printk("Health FastPeriodDivisor: %u\n", divisor);
+	}
+
+	return 0;
+}
+
+static int cmd_period_set(int argc, char *argv[])
+{
+	u8_t divisor, updated_divisor;
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	divisor = strtoul(argv[1], NULL, 0);
+
+	err = bt_mesh_health_period_set(net.net_idx, net.dst, net.app_idx,
+					divisor, &updated_divisor);
+	if (err) {
+		printk("Failed to send Health Period Set (err %d)\n", err);
+	} else {
+		printk("Health FastPeriodDivisor: %u\n", updated_divisor);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_period_set_help = {
+	NULL, "<divisor>", NULL
+};
+
+static int cmd_period_set_unack(int argc, char *argv[])
+{
+	u8_t divisor;
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	divisor = strtoul(argv[1], NULL, 0);
+
+	err = bt_mesh_health_period_set(net.net_idx, net.dst, net.app_idx,
+					divisor, NULL);
+	if (err) {
+		printk("Failed to send Health Period Set (err %d)\n", err);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_period_set_unack_help = {
+	NULL, "<divisor>", NULL
+};
+
+static int cmd_attention_get(int argc, char *argv[])
+{
+	u8_t attention;
+	int err;
+
+	err = bt_mesh_health_attention_get(net.net_idx, net.dst, net.app_idx,
+					   &attention);
+	if (err) {
+		printk("Failed to send Health Attention Get (err %d)\n", err);
+	} else {
+		printk("Health Attention Timer: %u\n", attention);
+	}
+
+	return 0;
+}
+
+static int cmd_attention_set(int argc, char *argv[])
+{
+	u8_t attention, updated_attention;
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	attention = strtoul(argv[1], NULL, 0);
+
+	err = bt_mesh_health_attention_set(net.net_idx, net.dst, net.app_idx,
+					   attention, &updated_attention);
+	if (err) {
+		printk("Failed to send Health Attention Set (err %d)\n", err);
+	} else {
+		printk("Health Attention Timer: %u\n", updated_attention);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_attention_set_help = {
+	NULL, "<timer>", NULL
+};
+
+static int cmd_attention_set_unack(int argc, char *argv[])
+{
+	u8_t attention;
+	int err;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	attention = strtoul(argv[1], NULL, 0);
+
+	err = bt_mesh_health_attention_set(net.net_idx, net.dst, net.app_idx,
+					   attention, NULL);
+	if (err) {
+		printk("Failed to send Health Attention Set (err %d)\n", err);
+	}
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_attention_set_unack_help = {
+	NULL, "<timer>", NULL
+};
+
+static int cmd_add_fault(int argc, char *argv[])
+{
+	u8_t fault_id;
+	u8_t i;
+
+	if (argc < 2) {
+		return -EINVAL;
+	}
+
+	fault_id = strtoul(argv[1], NULL, 0);
+	if (!fault_id) {
+		printk("The Fault ID must be non-zero!\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < sizeof(cur_faults); i++) {
+		if (!cur_faults[i]) {
+			cur_faults[i] = fault_id;
+			break;
+		}
+	}
+
+	if (i == sizeof(cur_faults)) {
+		printk("Fault array is full. Use \"del-fault\" to clear it\n");
+		return 0;
+	}
+
+	for (i = 0; i < sizeof(reg_faults); i++) {
+		if (!reg_faults[i]) {
+			reg_faults[i] = fault_id;
+			break;
+		}
+	}
+
+	if (i == sizeof(reg_faults)) {
+		printk("No space to store more registered faults\n");
+	}
+
+	bt_mesh_fault_update(&elements[0]);
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_add_fault_help = {
+	NULL, "<Fault ID>", NULL
+};
+
+static int cmd_del_fault(int argc, char *argv[])
+{
+	u8_t fault_id;
+	u8_t i;
+
+	if (argc < 2) {
+		memset(cur_faults, 0, sizeof(cur_faults));
+		printk("All current faults cleared\n");
+		bt_mesh_fault_update(&elements[0]);
+		return 0;
+	}
+
+	fault_id = strtoul(argv[1], NULL, 0);
+	if (!fault_id) {
+		printk("The Fault ID must be non-zero!\n");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < sizeof(cur_faults); i++) {
+		if (cur_faults[i] == fault_id) {
+			cur_faults[i] = 0;
+			printk("Fault cleared\n");
+		}
+	}
+
+	bt_mesh_fault_update(&elements[0]);
+
+	return 0;
+}
+
+struct shell_cmd_help cmd_del_fault_help = {
+	NULL, "[Fault ID]", NULL
+};
+
+static const struct shell_cmd mesh_commands[] = {
+	{ "init", cmd_init, NULL },
+	{ "timeout", cmd_timeout, &cmd_timeout_help },
+#if MYNEWT_VAL(BLE_MESH_PB_ADV)
+	{ "pb-adv", cmd_pb_adv, &cmd_pb_help },
+#endif
+#if MYNEWT_VAL(BLE_MESH_PB_GATT)
+	{ "pb-gatt", cmd_pb_gatt, &cmd_pb_help },
+#endif
+	{ "reset", cmd_reset, NULL },
+	{ "uuid", cmd_uuid, &cmd_uuid_help },
+	{ "input-num", cmd_input_num, &cmd_input_num_help },
+	{ "input-str", cmd_input_str, &cmd_input_str_help },
+	{ "static-oob", cmd_static_oob, &cmd_static_oob_help },
+	{ "provision", cmd_provision, &cmd_provision_help },
+#if MYNEWT_VAL(BLE_MESH_LOW_POWER)
+	{ "lpn", cmd_lpn, &cmd_lpn_help },
+	{ "poll", cmd_poll, NULL },
+#endif
+#if MYNEWT_VAL(BLE_MESH_GATT_PROXY)
+	{ "ident", cmd_ident, NULL },
+#endif
+	{ "dst", cmd_dst, &cmd_dst_help },
+	{ "netidx", cmd_netidx, &cmd_netidx_help },
+	{ "appidx", cmd_appidx, &cmd_appidx_help },
+
+	/* Commands which access internal APIs, for testing only */
+	{ "net-send", cmd_net_send, &cmd_net_send_help },
+	{ "iv-update", cmd_iv_update, NULL },
+	{ "iv-update-test", cmd_iv_update_test, &cmd_iv_update_test_help },
+	{ "rpl-clear", cmd_rpl_clear, NULL },
+
+	/* Configuration Client Model operations */
+	{ "get-comp", cmd_get_comp, &cmd_get_comp_help },
+	{ "beacon", cmd_beacon, &cmd_beacon_help },
+	{ "ttl", cmd_ttl, &cmd_ttl_help},
+	{ "friend", cmd_friend, &cmd_friend_help },
+	{ "gatt-proxy", cmd_gatt_proxy, &cmd_gatt_proxy_help },
+	{ "relay", cmd_relay, &cmd_relay_help },
+	{ "net-key-add", cmd_net_key_add, &cmd_net_key_add_help },
+	{ "app-key-add", cmd_app_key_add, &cmd_app_key_add_help },
+	{ "mod-app-bind", cmd_mod_app_bind, &cmd_mod_app_bind_help },
+	{ "mod-pub", cmd_mod_pub, &cmd_mod_pub_help },
+	{ "mod-sub-add", cmd_mod_sub_add, &cmd_mod_sub_add_help },
+	{ "mod-sub-del", cmd_mod_sub_del, &cmd_mod_sub_del_help },
+	{ "mod-sub-add-va", cmd_mod_sub_add_va, &cmd_mod_sub_add_va_help },
+	{ "mod-sub-del-va", cmd_mod_sub_del_va, &cmd_mod_sub_del_va_help },
+	{ "hb-sub", cmd_hb_sub, &cmd_hb_sub_help },
+	{ "hb-pub", cmd_hb_pub, &cmd_hb_pub_help },
+
+	/* Health Client Model Operations */
+	{ "fault-get", cmd_fault_get, &cmd_fault_get_help },
+	{ "fault-clear", cmd_fault_clear, &cmd_fault_clear_help },
+	{ "fault-clear-unack", cmd_fault_clear_unack, &cmd_fault_clear_unack_help },
+	{ "fault-test", cmd_fault_test, &cmd_fault_test_help },
+	{ "fault-test-unack", cmd_fault_test_unack, &cmd_fault_test_unack_help },
+	{ "period-get", cmd_period_get, NULL },
+	{ "period-set", cmd_period_set, &cmd_period_set_help },
+	{ "period-set-unack", cmd_period_set_unack, &cmd_period_set_unack_help },
+	{ "attention-get", cmd_attention_get, NULL },
+	{ "attention-set", cmd_attention_set, &cmd_attention_set_help },
+	{ "attention-set-unack", cmd_attention_set_unack, &cmd_attention_set_unack_help },
+
+	/* Health Server Model Operations */
+	{ "add-fault", cmd_add_fault, &cmd_add_fault_help },
+	{ "del-fault", cmd_del_fault, &cmd_del_fault_help },
+
+	{ NULL, NULL, NULL}
+};
+
+void mesh_shell_init(void)
+{
+	health_pub_init();
+	shell_register("mesh", mesh_commands);
+}
+
+#endif
diff --git a/net/nimble/host/mesh/src/shell.h b/net/nimble/host/mesh/src/shell.h
new file mode 100644
index 000000000..edd85a1f7
--- /dev/null
+++ b/net/nimble/host/mesh/src/shell.h
@@ -0,0 +1,6 @@
+#ifndef __SHELL_H__
+#define __SHELL_H__
+
+void mesh_shell_init(void);
+
+#endif
diff --git a/net/nimble/host/mesh/src/slist.c b/net/nimble/host/mesh/src/slist.c
new file mode 100644
index 000000000..719c0679e
--- /dev/null
+++ b/net/nimble/host/mesh/src/slist.c
@@ -0,0 +1,323 @@
+
+/*
+ * Copyright (c) 2016 Intel Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stddef.h>
+#include <stdbool.h>
+#include "mesh/slist.h"
+#include "os/queue.h"
+#include "os/os.h"
+#include "os/os_mbuf.h"
+
+
+/**
+ * @brief Initialize a list
+ *
+ * @param list A pointer on the list to initialize
+ */
+void sys_slist_init(sys_slist_t *list)
+{
+	STAILQ_INIT(list);
+}
+
+/**
+ * @brief Test if the given list is empty
+ *
+ * @param list A pointer on the list to test
+ *
+ * @return a boolean, true if it's empty, false otherwise
+ */
+bool sys_slist_is_empty(sys_slist_t *list)
+{
+	return STAILQ_EMPTY(list);
+}
+
+/**
+ * @brief Peek the first node from the list
+ *
+ * @param list A point on the list to peek the first node from
+ *
+ * @return A pointer on the first node of the list (or NULL if none)
+ */
+sys_snode_t *sys_slist_peek_head(sys_slist_t *list)
+{
+	struct os_mbuf_pkthdr *mp;
+	struct os_mbuf *m;
+
+	mp = STAILQ_FIRST(list);
+
+	if (mp) {
+		m = OS_MBUF_PKTHDR_TO_MBUF(mp);
+	} else {
+		m = NULL;
+	}
+
+	return m;
+}
+
+/**
+ * @brief Peek the last node from the list
+ *
+ * @param list A point on the list to peek the last node from
+ *
+ * @return A pointer on the last node of the list (or NULL if none)
+ */
+sys_snode_t *sys_slist_peek_tail(sys_slist_t *list)
+{
+	struct os_mbuf_pkthdr *mp;
+	struct os_mbuf *m;
+
+	mp = STAILQ_LAST(list, os_mbuf_pkthdr, omp_next);
+
+	if (mp) {
+		m = OS_MBUF_PKTHDR_TO_MBUF(mp);
+	} else {
+		m = NULL;
+	}
+
+	return m;
+}
+
+/**
+ * @brief Peek the next node from current node, node is not NULL
+ *
+ * Faster then sys_slist_peek_next() if node is known not to be NULL.
+ *
+ * @param node A pointer on the node where to peek the next node
+ *
+ * @return a pointer on the next node (or NULL if none)
+ */
+sys_snode_t *sys_slist_peek_next_no_check(sys_snode_t *node)
+{
+	struct os_mbuf_pkthdr *mp;
+	struct os_mbuf *m;
+
+	mp = OS_MBUF_PKTHDR(node);
+	if (!mp) {
+		return NULL;
+	}
+
+	mp = STAILQ_NEXT(mp, omp_next);
+
+	if (mp) {
+		m = OS_MBUF_PKTHDR_TO_MBUF(mp);
+	} else {
+		m = NULL;
+	}
+
+	return m;
+}
+
+/**
+ * @brief Peek the next node from current node
+ *
+ * @param node A pointer on the node where to peek the next node
+ *
+ * @return a pointer on the next node (or NULL if none)
+ */
+sys_snode_t *sys_slist_peek_next(sys_snode_t *node)
+{
+	return node ? sys_slist_peek_next_no_check(node) : NULL;
+}
+
+/**
+ * @brief Prepend a node to the given list
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param node A pointer on the node to prepend
+ */
+void sys_slist_prepend(sys_slist_t *list,
+				     sys_snode_t *node)
+{
+	struct os_mbuf_pkthdr *mp;
+
+	mp = OS_MBUF_PKTHDR(node);
+	if (!mp) {
+		return;
+	}
+
+	STAILQ_INSERT_HEAD(list, mp, omp_next);
+}
+
+/**
+ * @brief Append a node to the given list
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param node A pointer on the node to append
+ */
+void sys_slist_append(sys_slist_t *list,
+				    sys_snode_t *node)
+{
+	struct os_mbuf_pkthdr *mp;
+
+	mp = OS_MBUF_PKTHDR(node);
+	if (!mp) {
+		return;
+	}
+
+	STAILQ_INSERT_TAIL(list, mp, omp_next);
+}
+
+/**
+ * @brief Append a list to the given list
+ *
+ * Append a singly-linked, NULL-terminated list consisting of nodes containing
+ * the pointer to the next node as the first element of a node, to @a list.
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param head A pointer to the first element of the list to append
+ * @param tail A pointer to the last element of the list to append
+ */
+void sys_slist_append_list(sys_slist_t *list,
+					 sys_slist_t *list_append)
+{
+	struct os_mbuf_pkthdr *mp;
+
+	STAILQ_FOREACH(mp, list_append, omp_next) {
+		STAILQ_INSERT_TAIL(list, mp, omp_next);
+	}
+}
+
+/**
+ * @brief merge two slists, appending the second one to the first
+ *
+ * When the operation is completed, the appending list is empty.
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param list_to_append A pointer to the list to append.
+ */
+void sys_slist_merge_slist(sys_slist_t *list,
+					 sys_slist_t *list_to_append)
+{
+	sys_slist_append_list(list, list_to_append);
+	sys_slist_init(list_to_append);
+}
+
+/**
+ * @brief Insert a node to the given list
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param prev A pointer on the previous node
+ * @param node A pointer on the node to insert
+ */
+void sys_slist_insert(sys_slist_t *list,
+				    sys_snode_t *prev,
+				    sys_snode_t *node)
+{
+	struct os_mbuf_pkthdr *mp, *mp_prev;
+
+	if (!prev) {
+		sys_slist_prepend(list, node);
+	} else {
+		mp_prev = OS_MBUF_PKTHDR(prev);
+		mp = OS_MBUF_PKTHDR(node);
+		STAILQ_INSERT_AFTER(list, mp_prev, mp, omp_next);
+	}
+}
+
+/**
+ * @brief Fetch and remove the first node of the given list
+ *
+ * List must be known to be non-empty.
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ *
+ * @return A pointer to the first node of the list
+ */
+sys_snode_t *sys_slist_get_not_empty(sys_slist_t *list)
+{
+	struct os_mbuf_pkthdr *mp;
+	struct os_mbuf *m;
+
+	mp = STAILQ_FIRST(list);
+	m = OS_MBUF_PKTHDR_TO_MBUF(mp);
+
+	STAILQ_REMOVE_HEAD(list, omp_next);
+
+	return m;
+}
+
+/**
+ * @brief Fetch and remove the first node of the given list
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ *
+ * @return A pointer to the first node of the list (or NULL if empty)
+ */
+sys_snode_t *sys_slist_get(sys_slist_t *list)
+{
+	return sys_slist_is_empty(list) ? NULL : sys_slist_get_not_empty(list);
+}
+
+/**
+ * @brief Remove a node
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param prev_node A pointer on the previous node
+ *        (can be NULL, which means the node is the list's head)
+ * @param node A pointer on the node to remove
+ */
+void sys_slist_remove(sys_slist_t *list,
+				    sys_snode_t *prev_node,
+				    sys_snode_t *node)
+{
+	struct os_mbuf_pkthdr *mp;
+
+	mp = OS_MBUF_PKTHDR(node);
+	STAILQ_REMOVE(list, mp, os_mbuf_pkthdr, omp_next);
+}
+
+/**
+ * @brief Find and remove a node from a list
+ *
+ * This and other sys_slist_*() functions are not thread safe.
+ *
+ * @param list A pointer on the list to affect
+ * @param node A pointer on the node to remove from the list
+ *
+ * @return true if node was removed
+ */
+bool sys_slist_find_and_remove(sys_slist_t *list,
+					     sys_snode_t *node)
+{
+	sys_snode_t *prev = NULL;
+	sys_snode_t *test;
+
+	SYS_SLIST_FOR_EACH_NODE(list, test) {
+		if (test == node) {
+			sys_slist_remove(list, prev, node);
+			return true;
+		}
+
+		prev = test;
+	}
+
+	return false;
+
+}
+
+void net_buf_slist_put(sys_slist_t *list, struct os_mbuf *buf)
+{
+	sys_slist_append(list, buf);
+}
+
+struct os_mbuf *net_buf_slist_get(sys_slist_t *list)
+{
+	return sys_slist_get(list);
+}
diff --git a/net/nimble/host/mesh/src/transport.c b/net/nimble/host/mesh/src/transport.c
index ebc2d72ec..013946bba 100644
--- a/net/nimble/host/mesh/src/transport.c
+++ b/net/nimble/host/mesh/src/transport.c
@@ -31,9 +31,9 @@
 #define SEG(data)                   ((data)[0] >> 7)
 #define AKF(data)                   (((data)[0] >> 6) & 0x01)
 #define AID(data)                   ((data)[0] & AID_MASK)
-#define MIC_SIZE(data)              (((data)[1] & 0x80) ? 8 : 4)
+#define ASZMIC(data)                (((data)[1] >> 7) & 1)
 
-#define SZMIC(mic_len)              (mic_len == 8 ? 1 : 0)
+#define APP_MIC_LEN(aszmic)         ((aszmic) ? 8 : 4)
 
 #define UNSEG_HDR(akf, aid)         ((akf << 6) | (aid & AID_MASK))
 #define SEG_HDR(akf, aid)           (UNSEG_HDR(akf, aid) | 0x80)
@@ -56,9 +56,10 @@ static struct seg_tx {
 	struct os_mbuf          *seg[BT_MESH_TX_SEG_COUNT];
 	u64_t                    seq_auth;
 	u16_t                    dst;
-	u8_t                     seg_n;      /* Last segment index */
-	u8_t                     nack_count; /* Number of not acked segments */
-	bt_mesh_cb_t             cb;
+	u8_t                     seg_n:5,       /* Last segment index */
+				 new_key:1;     /* New/old key */
+	u8_t                     nack_count;    /* Number of unacked segs */
+	const struct bt_mesh_send_cb *cb;
 	void                    *cb_data;
 	struct k_delayed_work    retransmit; /* Retransmit timer */
 } seg_tx[MYNEWT_VAL(BLE_MESH_TX_SEG_MSG_COUNT)];
@@ -68,7 +69,8 @@ static struct seg_rx {
 	u64_t                    seq_auth;
 	u8_t                     seg_n:5,
 				 ctl:1,
-				 in_use:1;
+				 in_use:1,
+				 obo:1;
 	u8_t                     hdr;
 	u8_t                     ttl;
 	u16_t                    src;
@@ -81,18 +83,24 @@ static struct seg_rx {
 	[0 ... (MYNEWT_VAL(BLE_MESH_RX_SEG_MSG_COUNT) - 1)] = { 0 },
 };
 
-static int send_unseg(struct bt_mesh_net_tx *tx, u8_t aid,
-		      struct os_mbuf *sdu)
+static u16_t hb_sub_dst = BT_MESH_ADDR_UNASSIGNED;
+
+void bt_mesh_set_hb_sub_dst(u16_t addr)
+{
+	hb_sub_dst = addr;
+}
+
+static int send_unseg(struct bt_mesh_net_tx *tx, struct os_mbuf *sdu,
+		      const struct bt_mesh_send_cb *cb, void *cb_data)
 {
 	struct os_mbuf *buf;
-	u8_t xmit;
 
 	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);
 
-	xmit = bt_mesh_net_transmit_get();
-	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, TRANSMIT_COUNT(xmit),
-				 TRANSMIT_INT(xmit), BUF_TIMEOUT);
+	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA,
+				 BT_MESH_TRANSMIT_COUNT(tx->xmit),
+				 BT_MESH_TRANSMIT_INT(tx->xmit), BUF_TIMEOUT);
 	if (!buf) {
 		BT_ERR("Out of network buffers");
 		return -ENOBUFS;
@@ -101,14 +109,26 @@ static int send_unseg(struct bt_mesh_net_tx *tx, u8_t aid,
 	net_buf_reserve(buf, BT_MESH_NET_HDR_LEN);
 
 	if (tx->ctx->app_idx == BT_MESH_KEY_DEV) {
-		net_buf_simple_add_u8(buf, UNSEG_HDR(0, 0));
+		net_buf_add_u8(buf, UNSEG_HDR(0, 0));
 	} else {
-		net_buf_simple_add_u8(buf, UNSEG_HDR(1, aid));
+		net_buf_add_u8(buf, UNSEG_HDR(1, tx->aid));
 	}
 
 	net_buf_add_mem(buf, sdu->om_data, sdu->om_len);
 
-	return bt_mesh_net_send(tx, buf, NULL);
+	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
+		if (bt_mesh_friend_enqueue_tx(tx, BT_MESH_FRIEND_PDU_SINGLE,
+					      NULL, buf) &&
+		    BT_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) {
+			/* PDUs for a specific Friend should only go
+			 * out through the Friend Queue.
+			 */
+			net_buf_unref(buf);
+			return 0;
+		}
+	}
+
+	return bt_mesh_net_send(tx, buf, cb, cb_data);
 }
 
 bool bt_mesh_tx_in_progress(void)
@@ -154,29 +174,49 @@ static void seg_tx_reset(struct seg_tx *tx)
 	if (bt_mesh.pending_update) {
 		BT_DBG("Proceding with pending IV Update");
 		bt_mesh.pending_update = 0;
-		/* bt_mesh_iv_update() will re-enable the flag if this
+		/* bt_mesh_net_iv_update() will re-enable the flag if this
 		 * wasn't the only transfer.
 		 */
-		bt_mesh_iv_update(bt_mesh.iv_index, false);
+		if (bt_mesh_net_iv_update(bt_mesh.iv_index, false)) {
+			bt_mesh_net_sec_update(NULL);
+		}
 	}
 }
 
 static inline void seg_tx_complete(struct seg_tx *tx, int err)
 {
-	if (tx->cb) {
-		tx->cb(err, tx->cb_data);
+	if (tx->cb && tx->cb->end) {
+		tx->cb->end(err, tx->cb_data);
 	}
 
 	seg_tx_reset(tx);
 }
 
-static void seg_sent(struct os_mbuf *buf, int err)
+static void seg_send_start(u16_t duration, int err, void *user_data)
+{
+	struct seg_tx *tx = user_data;
+
+	if (tx->cb && tx->cb->start) {
+		tx->cb->start(duration, err, tx->cb_data);
+	}
+}
+
+static void seg_sent(int err, void *user_data)
 {
-	struct seg_tx *tx = &seg_tx[BT_MESH_ADV(buf)->seg.tx_id];
+	struct seg_tx *tx = user_data;
 
 	k_delayed_work_submit(&tx->retransmit, SEG_RETRANSMIT_TIMEOUT);
 }
 
+static const struct bt_mesh_send_cb first_sent_cb = {
+	.start = seg_send_start,
+	.end = seg_sent,
+};
+
+static const struct bt_mesh_send_cb seg_sent_cb = {
+	.end = seg_sent,
+};
+
 static void seg_tx_send_unacked(struct seg_tx *tx)
 {
 	int i, err;
@@ -201,10 +241,8 @@ static void seg_tx_send_unacked(struct seg_tx *tx)
 
 		BT_DBG("resending %u/%u", i, tx->seg_n);
 
-		err = bt_mesh_net_resend(tx->sub, seg,
-					 BT_MESH_ADV(seg)->seg.new_key,
-					 BT_MESH_ADV(seg)->seg.friend_cred,
-					 seg_sent);
+		err = bt_mesh_net_resend(tx->sub, seg, tx->new_key,
+					 &seg_sent_cb, tx);
 		if (err) {
 			BT_ERR("Sending segment failed");
 			seg_tx_complete(tx, -EIO);
@@ -219,18 +257,17 @@ static void seg_retransmit(struct os_event *work)
 	seg_tx_send_unacked(tx);
 }
 
-static int send_seg(struct bt_mesh_net_tx *net_tx, u8_t aid,
-		    u8_t mic_len, struct os_mbuf *sdu,
-		    bt_mesh_cb_t cb, void *cb_data)
+static int send_seg(struct bt_mesh_net_tx *net_tx, struct os_mbuf *sdu,
+		    const struct bt_mesh_send_cb *cb, void *cb_data)
 {
 	u8_t seg_hdr, seg_o;
 	u16_t seq_zero;
 	struct seg_tx *tx;
 	int i;
 
-	BT_DBG("src 0x%04x dst 0x%04x app_idx 0x%04x mic_len %u sdu_len %u",
-	       net_tx->src, net_tx->ctx->addr, net_tx->ctx->app_idx, mic_len,
-	       sdu->om_len);
+	BT_DBG("src 0x%04x dst 0x%04x app_idx 0x%04x aszmic %u sdu_len %u",
+	       net_tx->src, net_tx->ctx->addr, net_tx->ctx->app_idx,
+	       net_tx->aszmic, sdu->om_len);
 
 	if (sdu->om_len < 1) {
 		BT_ERR("Zero-length SDU not allowed");
@@ -257,7 +294,7 @@ static int send_seg(struct bt_mesh_net_tx *net_tx, u8_t aid,
 	if (net_tx->ctx->app_idx == BT_MESH_KEY_DEV) {
 		seg_hdr = SEG_HDR(0, 0);
 	} else {
-		seg_hdr = SEG_HDR(1, aid);
+		seg_hdr = SEG_HDR(1, net_tx->aid);
 	}
 
 	seg_o = 0;
@@ -266,6 +303,9 @@ static int send_seg(struct bt_mesh_net_tx *net_tx, u8_t aid,
 	tx->nack_count = tx->seg_n + 1;
 	tx->seq_auth = SEQ_AUTH(BT_MESH_NET_IVI_TX, bt_mesh.seq);
 	tx->sub = net_tx->sub;
+	tx->new_key = net_tx->sub->kr_flag;
+	tx->cb = cb;
+	tx->cb_data = cb_data;
 
 	seq_zero = tx->seq_auth & 0x1fff;
 
@@ -274,30 +314,27 @@ static int send_seg(struct bt_mesh_net_tx *net_tx, u8_t aid,
 	for (seg_o = 0; sdu->om_len; seg_o++) {
 		struct os_mbuf *seg;
 		u16_t len;
-		u8_t xmit;
 		int err;
 
-		xmit = bt_mesh_net_transmit_get();
-		seg = bt_mesh_adv_create(BT_MESH_ADV_DATA, TRANSMIT_COUNT(xmit),
-					 TRANSMIT_INT(xmit), BUF_TIMEOUT);
+		seg = bt_mesh_adv_create(BT_MESH_ADV_DATA,
+					 BT_MESH_TRANSMIT_COUNT(net_tx->xmit),
+					 BT_MESH_TRANSMIT_INT(net_tx->xmit),
+					 BUF_TIMEOUT);
 		if (!seg) {
 			BT_ERR("Out of segment buffers");
 			seg_tx_reset(tx);
 			return -ENOBUFS;
 		}
 
-		BT_MESH_ADV(seg)->seg.tx_id = tx - seg_tx;
 		BT_MESH_ADV(seg)->seg.attempts = SEG_RETRANSMIT_ATTEMPTS;
-		BT_MESH_ADV(seg)->seg.new_key = net_tx->sub->kr_flag;
-		BT_MESH_ADV(seg)->seg.friend_cred = net_tx->ctx->friend_cred;
 
 		net_buf_reserve(seg, BT_MESH_NET_HDR_LEN);
 
-		net_buf_simple_add_u8(seg, seg_hdr);
-		net_buf_simple_add_u8(seg, (SZMIC(mic_len) << 7) | seq_zero >> 6);
-		net_buf_simple_add_u8(seg, (((seq_zero & 0x3f) << 2) |
+		net_buf_add_u8(seg, seg_hdr);
+		net_buf_add_u8(seg, (net_tx->aszmic << 7) | seq_zero >> 6);
+		net_buf_add_u8(seg, (((seq_zero & 0x3f) << 2) |
 				     (seg_o >> 3)));
-		net_buf_simple_add_u8(seg, ((seg_o & 0x07) << 5) | tx->seg_n);
+		net_buf_add_u8(seg, ((seg_o & 0x07) << 5) | tx->seg_n);
 
 		len = min(sdu->om_len, 12);
 		net_buf_add_mem(seg, sdu->om_data, len);
@@ -305,9 +342,32 @@ static int send_seg(struct bt_mesh_net_tx *net_tx, u8_t aid,
 
 		tx->seg[seg_o] = net_buf_ref(seg);
 
+		if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
+			enum bt_mesh_friend_pdu_type type;
+
+			if (seg_o == tx->seg_n) {
+				type = BT_MESH_FRIEND_PDU_COMPLETE;
+			} else {
+				type = BT_MESH_FRIEND_PDU_PARTIAL;
+			}
+
+			if (bt_mesh_friend_enqueue_tx(net_tx, type,
+						      &tx->seq_auth,
+						      seg) &&
+			    BT_MESH_ADDR_IS_UNICAST(net_tx->ctx->addr)) {
+				/* PDUs for a specific Friend should only go
+				 * out through the Friend Queue.
+				 */
+				net_buf_unref(seg);
+				return 0;
+			}
+		}
+
 		BT_DBG("Sending %u/%u", seg_o, tx->seg_n);
 
-		err = bt_mesh_net_send(net_tx, seg, seg_sent);
+		err = bt_mesh_net_send(net_tx, seg,
+				       seg_o ? &seg_sent_cb : &first_sent_cb,
+				       tx);
 		if (err) {
 			BT_ERR("Sending segment failed");
 			seg_tx_reset(tx);
@@ -316,7 +376,7 @@ static int send_seg(struct bt_mesh_net_tx *net_tx, u8_t aid,
 	}
 
 	if (bt_mesh_lpn_established()) {
-		bt_mesh_lpn_friend_poll();
+		bt_mesh_lpn_poll();
 	}
 
 	return 0;
@@ -339,21 +399,28 @@ struct bt_mesh_app_key *bt_mesh_app_key_find(u16_t app_idx)
 }
 
 int bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct os_mbuf *msg,
-		       bt_mesh_cb_t cb, void *cb_data)
+		       const struct bt_mesh_send_cb *cb, void *cb_data)
 {
-	bool seg = (msg->om_len > 11 || cb);
 	const u8_t *key;
-	u8_t mic_len, aid, aszmic;
 	u8_t *ad;
 	int err;
 
+	if (net_buf_simple_tailroom(msg) < 4) {
+		BT_ERR("Insufficient tailroom for Transport MIC");
+		return -EINVAL;
+	}
+
+	if (msg->om_len > 11) {
+		tx->ctx->send_rel = 1;
+	}
+
 	BT_DBG("net_idx 0x%04x app_idx 0x%04x dst 0x%04x", tx->sub->net_idx,
 	       tx->ctx->app_idx, tx->ctx->addr);
 	BT_DBG("len %u: %s", msg->om_len, bt_hex(msg->om_data, msg->om_len));
 
 	if (tx->ctx->app_idx == BT_MESH_KEY_DEV) {
 		key = bt_mesh.dev_key;
-		aid = 0;
+		tx->aid = 0;
 	} else {
 		struct bt_mesh_app_key *app_key;
 
@@ -365,19 +432,17 @@ int bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct os_mbuf *msg,
 		if (tx->sub->kr_phase == BT_MESH_KR_PHASE_2 &&
 		    app_key->updated) {
 			key = app_key->keys[1].val;
-			aid = app_key->keys[1].id;
+			tx->aid = app_key->keys[1].id;
 		} else {
 			key = app_key->keys[0].val;
-			aid = app_key->keys[0].id;
+			tx->aid = app_key->keys[0].id;
 		}
 	}
 
-	if (!seg || net_buf_simple_tailroom(msg) < 8) {
-		mic_len = 4;
-		aszmic = 0;
+	if (!tx->ctx->send_rel || net_buf_simple_tailroom(msg) < 8) {
+		tx->aszmic = 0;
 	} else {
-		mic_len = 8;
-		aszmic = 1;
+		tx->aszmic = 1;
 	}
 
 	if (BT_MESH_ADDR_IS_VIRTUAL(tx->ctx->addr)) {
@@ -387,18 +452,39 @@ int bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct os_mbuf *msg,
 	}
 
 	err = bt_mesh_app_encrypt(key, tx->ctx->app_idx == BT_MESH_KEY_DEV,
-				  aszmic, msg, ad, mic_len, tx->src,
+				  tx->aszmic, msg, ad, tx->src,
 				  tx->ctx->addr, bt_mesh.seq,
 				  BT_MESH_NET_IVI_TX);
 	if (err) {
 		return err;
 	}
 
-	if (seg) {
-		return send_seg(tx, aid, mic_len, msg, cb, cb_data);
+	if (tx->ctx->send_rel) {
+		err = send_seg(tx, msg, cb, cb_data);
+	} else {
+		err = send_unseg(tx, msg, cb, cb_data);
 	}
 
-	return send_unseg(tx, aid, msg);
+	return err;
+}
+
+int bt_mesh_trans_resend(struct bt_mesh_net_tx *tx, struct os_mbuf *msg,
+			 const struct bt_mesh_send_cb *cb, void *cb_data)
+{
+	struct net_buf_simple_state state;
+	int err;
+
+	net_buf_simple_save(msg, &state);
+
+	if (tx->ctx->send_rel || msg->om_len > 15) {
+		err = send_seg(tx, msg, cb, cb_data);
+	} else {
+		err = send_unseg(tx, msg, cb, cb_data);
+	}
+
+	net_buf_simple_restore(msg, &state);
+
+	return err;
 }
 
 static bool is_replay(struct bt_mesh_net_rx *rx)
@@ -422,7 +508,8 @@ static bool is_replay(struct bt_mesh_net_rx *rx)
 				return true;
 			}
 
-			if (rpl->seq < rx->seq) {
+			if ((!rx->old_iv && rpl->old_iv) ||
+			    rpl->seq < rx->seq) {
 				rpl->seq = rx->seq;
 				rpl->old_iv = rx->old_iv;
 				return false;
@@ -436,8 +523,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 mic_size,
-		    u8_t aszmic, struct os_mbuf *buf)
+static int sdu_recv(struct bt_mesh_net_rx *rx, 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);
@@ -445,16 +532,20 @@ static int sdu_recv(struct bt_mesh_net_rx *rx, u8_t hdr, u8_t mic_size,
 	u16_t i;
 	int err = 0;
 
-	BT_DBG("MIC_SIZE %u ASZMIC %u AKF %u AID 0x%02x",
-	       mic_size, aszmic, AKF(&hdr), AID(&hdr));
+	BT_DBG("ASZMIC %u AKF %u AID 0x%02x", aszmic, AKF(&hdr), AID(&hdr));
 	BT_DBG("len %u: %s", buf->om_len, bt_hex(buf->om_data, buf->om_len));
 
-	if (buf->om_len < 1 + mic_size) {
+	if (buf->om_len < 1 + APP_MIC_LEN(aszmic)) {
 		BT_ERR("Too short SDU + MIC");
 		err = -EINVAL;
 		goto done;
 	}
 
+	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && !rx->local_match) {
+		BT_DBG("Ignoring PDU for LPN 0x%04x of this Friend", rx->dst);
+		return 0;
+	}
+
 	if (BT_MESH_ADDR_IS_VIRTUAL(rx->dst)) {
 		ad = bt_mesh_label_uuid_get(rx->dst);
 	} else {
@@ -462,14 +553,13 @@ static int sdu_recv(struct bt_mesh_net_rx *rx, u8_t hdr, u8_t mic_size,
 	}
 
 	/* Adjust the length to not contain the MIC at the end */
-	buf->om_len -= mic_size;
+	buf->om_len -= APP_MIC_LEN(aszmic);
 
 	if (!AKF(&hdr)) {
 		net_buf_simple_init(sdu, 0);
 		err = bt_mesh_app_decrypt(bt_mesh.dev_key, true, aszmic, buf,
-					  mic_size, sdu, ad, rx->ctx.addr,
-					  rx->dst, rx->seq,
-					  BT_MESH_NET_IVI_RX(rx));
+					  sdu, ad, rx->ctx.addr, rx->dst,
+					  rx->seq, BT_MESH_NET_IVI_RX(rx));
 		if (err) {
 			BT_ERR("Unable to decrypt with DevKey");
 			err = -EINVAL;
@@ -503,9 +593,8 @@ static int sdu_recv(struct bt_mesh_net_rx *rx, u8_t hdr, u8_t mic_size,
 
 		net_buf_simple_init(sdu, 0);
 		err = bt_mesh_app_decrypt(keys->val, false, aszmic, buf,
-					  mic_size, sdu, ad, rx->ctx.addr,
-					  rx->dst, rx->seq,
-					  BT_MESH_NET_IVI_RX(rx));
+					  sdu, ad, rx->ctx.addr, rx->dst,
+					  rx->seq, BT_MESH_NET_IVI_RX(rx));
 		if (err) {
 			BT_WARN("Unable to decrypt with AppKey %u", i);
 			continue;
@@ -514,13 +603,6 @@ static int sdu_recv(struct bt_mesh_net_rx *rx, u8_t hdr, u8_t mic_size,
 
 		rx->ctx.app_idx = key->app_idx;
 
-		if (is_replay(rx)) {
-			BT_WARN("Replay: src 0x%04x dst 0x%04x seq 0x%06x",
-				rx->ctx.addr, rx->dst, rx->seq);
-			err = -EINVAL;
-			goto done;
-		}
-
 		bt_mesh_model_recv(rx, sdu);
 		goto done;
 	}
@@ -564,7 +646,7 @@ static struct seg_tx *seg_tx_lookup(u16_t seq_zero, u8_t obo, u16_t addr)
 }
 
 static int trans_ack(struct bt_mesh_net_rx *rx, u8_t hdr,
-		     struct os_mbuf *buf)
+		     struct os_mbuf *buf, u64_t *seq_auth)
 {
 	struct seg_tx *tx;
 	unsigned int bit;
@@ -581,6 +663,13 @@ static int trans_ack(struct bt_mesh_net_rx *rx, u8_t hdr,
 	obo = seq_zero >> 15;
 	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);
+		/* 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;
+	}
+
 	ack = net_buf_simple_pull_be32(buf);
 
 	BT_DBG("OBO %u seq_zero 0x%04x ack 0x%08x", obo, seq_zero, ack);
@@ -591,6 +680,8 @@ static int trans_ack(struct bt_mesh_net_rx *rx, u8_t hdr,
 		return -EINVAL;
 	}
 
+	*seq_auth = tx->seq_auth;
+
 	if (!ack) {
 		BT_WARN("SDU canceled");
 		seg_tx_complete(tx, -ECANCELED);
@@ -636,6 +727,11 @@ static int trans_heartbeat(struct bt_mesh_net_rx *rx,
 		return -EINVAL;
 	}
 
+	if (rx->dst != hb_sub_dst) {
+		BT_WARN("Ignoring heartbeat to non-subscribed destination");
+		return 0;
+	}
+
 	init_ttl = (net_buf_simple_pull_u8(buf) & 0x7f);
 	feat = net_buf_simple_pull_be16(buf);
 
@@ -651,7 +747,7 @@ static int trans_heartbeat(struct bt_mesh_net_rx *rx,
 }
 
 static int ctl_recv(struct bt_mesh_net_rx *rx, u8_t hdr,
-		    struct os_mbuf *buf)
+		    struct os_mbuf *buf, u64_t *seq_auth)
 {
 	u8_t ctl_op = TRANS_CTL_OP(&hdr);
 
@@ -659,19 +755,32 @@ static int ctl_recv(struct bt_mesh_net_rx *rx, u8_t hdr,
 
 	switch (ctl_op) {
 	case TRANS_CTL_OP_ACK:
-		return trans_ack(rx, hdr, buf);
+		return trans_ack(rx, hdr, buf, seq_auth);
 	case TRANS_CTL_OP_HEARTBEAT:
 		return trans_heartbeat(rx, buf);
 	}
 
-#if (MYNEWT_VAL(BLE_MESH_FRIEND))
-	switch (ctl_op) {
-	case TRANS_CTL_OP_FRIEND_POLL:
-		return bt_mesh_friend_poll(rx, buf);
-	case TRANS_CTL_OP_FRIEND_REQ:
-		return bt_mesh_friend_req(rx, buf);
+	/* Only acks and heartbeats may need processing without local_match */
+	if (!rx->local_match) {
+		return 0;
+	}
+
+	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && !bt_mesh_lpn_established()) {
+		switch (ctl_op) {
+		case TRANS_CTL_OP_FRIEND_POLL:
+			return bt_mesh_friend_poll(rx, buf);
+		case TRANS_CTL_OP_FRIEND_REQ:
+			return bt_mesh_friend_req(rx, buf);
+		case TRANS_CTL_OP_FRIEND_CLEAR:
+			return bt_mesh_friend_clear(rx, buf);
+		case TRANS_CTL_OP_FRIEND_CLEAR_CFM:
+			return bt_mesh_friend_clear_cfm(rx, buf);
+		case TRANS_CTL_OP_FRIEND_SUB_ADD:
+			return bt_mesh_friend_sub_add(rx, buf);
+		case TRANS_CTL_OP_FRIEND_SUB_REM:
+			return bt_mesh_friend_sub_rem(rx, buf);
+		}
 	}
-#endif
 
 #if (MYNEWT_VAL(BLE_MESH_LOW_POWER))
 	if (ctl_op == TRANS_CTL_OP_FRIEND_OFFER) {
@@ -683,7 +792,7 @@ static int ctl_recv(struct bt_mesh_net_rx *rx, u8_t hdr,
 			return bt_mesh_lpn_friend_clear_cfm(rx, buf);
 		}
 
-		if (!rx->ctx.friend_cred) {
+		if (!rx->friend_cred) {
 			BT_WARN("Message from friend with wrong credentials");
 			return -EINVAL;
 		}
@@ -702,7 +811,8 @@ static int ctl_recv(struct bt_mesh_net_rx *rx, u8_t hdr,
 	return -ENOENT;
 }
 
-static int trans_unseg(struct os_mbuf *buf, struct bt_mesh_net_rx *rx)
+static int trans_unseg(struct os_mbuf *buf, struct bt_mesh_net_rx *rx,
+		       u64_t *seq_auth)
 {
 	u8_t hdr;
 
@@ -713,12 +823,23 @@ static int trans_unseg(struct os_mbuf *buf, struct bt_mesh_net_rx *rx)
 		return -EINVAL;
 	}
 
+	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);
+		return -EINVAL;
+	}
+
 	hdr = net_buf_simple_pull_u8(buf);
 
 	if (rx->ctl) {
-		return ctl_recv(rx, hdr, buf);
+		return ctl_recv(rx, hdr, buf, seq_auth);
 	} else {
-		return sdu_recv(rx, hdr, 4, 0, buf);
+		/* SDUs must match a local element or an LPN of this Friend. */
+		if (!rx->local_match && !rx->friend_match) {
+			return 0;
+		}
+
+		return sdu_recv(rx, hdr, 0, buf);
 	}
 }
 
@@ -726,8 +847,13 @@ static inline s32_t ack_timeout(struct seg_rx *rx)
 {
 	s32_t to;
 
+	/* The acknowledgment timer shall be set to a minimum of
+	 * 150 + 50 * TTL milliseconds.
+	 */
+	to = K_MSEC(150 + (50 * rx->ttl));
+
 	/* 100 ms for every not yet received segment */
-	to = K_MSEC(((rx->seg_n + 1) - popcount(rx->block)) * 100);
+	to += K_MSEC(((rx->seg_n + 1) - popcount(rx->block)) * 100);
 
 	/* Make sure we don't send more frequently than the duration for
 	 * each packet (default is 300ms).
@@ -736,18 +862,18 @@ static inline s32_t ack_timeout(struct seg_rx *rx)
 }
 
 int bt_mesh_ctl_send(struct bt_mesh_net_tx *tx, u8_t ctl_op, void *data,
-		     size_t data_len, bt_mesh_adv_func_t cb)
+		     size_t data_len, u64_t *seq_auth,
+		     const struct bt_mesh_send_cb *cb, void *cb_data)
 {
 	struct os_mbuf *buf;
-	u8_t xmit;
 
 	BT_DBG("src 0x%04x dst 0x%04x ttl 0x%02x ctl 0x%02x", tx->src,
 	       tx->ctx->addr, tx->ctx->send_ttl, ctl_op);
 	BT_DBG("len %zu: %s", data_len, bt_hex(data, data_len));
 
-	xmit = bt_mesh_net_transmit_get();
-	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA, TRANSMIT_COUNT(xmit),
-				 TRANSMIT_INT(xmit), BUF_TIMEOUT);
+	buf = bt_mesh_adv_create(BT_MESH_ADV_DATA,
+				 BT_MESH_TRANSMIT_COUNT(tx->xmit),
+				 BT_MESH_TRANSMIT_INT(tx->xmit), BUF_TIMEOUT);
 	if (!buf) {
 		BT_ERR("Out of transport buffers");
 		return -ENOBUFS;
@@ -755,15 +881,27 @@ int bt_mesh_ctl_send(struct bt_mesh_net_tx *tx, u8_t ctl_op, void *data,
 
 	net_buf_reserve(buf, BT_MESH_NET_HDR_LEN);
 
-	net_buf_simple_add_u8(buf, TRANS_CTL_HDR(ctl_op, 0));
+	net_buf_add_u8(buf, TRANS_CTL_HDR(ctl_op, 0));
 
 	net_buf_add_mem(buf, data, data_len);
 
-	return bt_mesh_net_send(tx, buf, cb);
+	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
+		if (bt_mesh_friend_enqueue_tx(tx, BT_MESH_FRIEND_PDU_SINGLE,
+					      seq_auth, buf) &&
+		    BT_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) {
+			/* PDUs for a specific Friend should only go
+			 * out through the Friend Queue.
+			 */
+			net_buf_unref(buf);
+			return 0;
+		}
+	}
+
+	return bt_mesh_net_send(tx, buf, cb, cb_data);
 }
 
 static int send_ack(struct bt_mesh_subnet *sub, u16_t src, u16_t dst,
-		    u8_t ttl, u16_t seq_zero, u32_t block)
+		    u8_t ttl, u64_t *seq_auth, u32_t block, u8_t obo)
 {
 	struct bt_mesh_msg_ctx ctx = {
 		.net_idx = sub->net_idx,
@@ -774,11 +912,13 @@ static int send_ack(struct bt_mesh_subnet *sub, u16_t src, u16_t dst,
 	struct bt_mesh_net_tx tx = {
 		.sub = sub,
 		.ctx = &ctx,
-		.src = src,
+		.src = obo ? bt_mesh_primary_addr() : src,
+		.xmit = bt_mesh_net_transmit_get(),
 	};
+	u16_t seq_zero = *seq_auth & 0x1fff;
 	u8_t buf[6];
 
-	BT_DBG("SeqZero 0x%04x Block 0x%08x", seq_zero, block);
+	BT_DBG("SeqZero 0x%04x Block 0x%08x OBO %u", seq_zero, block, obo);
 
 	if (bt_mesh_lpn_established()) {
 		BT_WARN("Not sending ack when LPN is enabled");
@@ -793,10 +933,11 @@ static int send_ack(struct bt_mesh_subnet *sub, u16_t src, u16_t dst,
 		return 0;
 	}
 
-	sys_put_be16(((seq_zero << 2) & 0x7ffc), buf);
+	sys_put_be16(((seq_zero << 2) & 0x7ffc) | (obo << 15), buf);
 	sys_put_be32(block, &buf[2]);
 
-	return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_ACK, buf, sizeof(buf), NULL);
+	return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_ACK, buf, sizeof(buf),
+				NULL, NULL, NULL);
 }
 
 static void seg_rx_reset(struct seg_rx *rx)
@@ -805,6 +946,13 @@ static void seg_rx_reset(struct seg_rx *rx)
 
 	k_delayed_work_cancel(&rx->ack);
 
+	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && rx->obo &&
+	    rx->block != BLOCK_COMPLETE(rx->seg_n)) {
+		BT_WARN("Clearing incomplete buffers from Friend queue");
+		bt_mesh_friend_clear_incomplete(rx->sub, rx->src, rx->dst,
+						&rx->seq_auth);
+	}
+
 	/* We don't reset rx->net and rx->seq_auth here since we need to
 	 * be able to send an ack if we receive a segment after we've
 	 * already received the full SDU.
@@ -822,13 +970,13 @@ static void seg_ack(struct os_event *work)
 	if (k_uptime_get_32() - rx->last > (60 * MSEC_PER_SEC)) {
 		BT_WARN("Incomplete timer expired");
 		send_ack(rx->sub, rx->dst, rx->src, rx->ttl,
-			 rx->seq_auth & 0x1fff, 0);
+			 &rx->seq_auth, 0, rx->obo);
 		seg_rx_reset(rx);
 		return;
 	}
 
-	send_ack(rx->sub, rx->dst, rx->src, rx->ttl, rx->seq_auth & 0x1fff,
-		 rx->block);
+	send_ack(rx->sub, rx->dst, rx->src, rx->ttl, &rx->seq_auth,
+		 rx->block, rx->obo);
 
 	k_delayed_work_submit(&rx->ack, ack_timeout(rx));
 }
@@ -871,7 +1019,9 @@ static struct seg_rx *seg_rx_find(struct bt_mesh_net_rx *net_rx,
 			 * has apparently started sending a new SDU.
 			 */
 			seg_rx_reset(rx);
-			return rx;
+
+			/* Return non-match so caller can re-allocate */
+			return NULL;
 		}
 	}
 
@@ -933,11 +1083,11 @@ static struct seg_rx *seg_rx_alloc(struct bt_mesh_net_rx *net_rx,
 	return NULL;
 }
 
-static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx)
+static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx,
+		     enum bt_mesh_friend_pdu_type *pdu_type, u64_t *seq_auth)
 {
 	struct seg_rx *rx;
-	u64_t seq_auth;
-	u8_t *hdr = buf->om_data;
+       u8_t *hdr = buf->om_data;
 	u16_t seq_zero;
 	u8_t seg_n;
 	u8_t seg_o;
@@ -948,8 +1098,7 @@ static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx)
 		return -EINVAL;
 	}
 
-	BT_DBG("MIC_SIZE %u AKF %u AID 0x%02x", MIC_SIZE(hdr), AKF(hdr),
-	       AID(hdr));
+	BT_DBG("ASZMIC %u AKF %u AID 0x%02x", ASZMIC(hdr), AKF(hdr), AID(hdr));
 
 	net_buf_simple_pull(buf, 1);
 
@@ -967,11 +1116,11 @@ static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx)
 		return -EINVAL;
 	}
 
-	seq_auth = SEQ_AUTH(BT_MESH_NET_IVI_RX(net_rx),
-			    (net_rx->seq & 0xffffe000) | seq_zero);
+	*seq_auth = SEQ_AUTH(BT_MESH_NET_IVI_RX(net_rx),
+			     (net_rx->seq & 0xffffe000) | seq_zero);
 
 	/* Look for old RX sessions */
-	rx = seg_rx_find(net_rx, &seq_auth);
+	rx = seg_rx_find(net_rx, seq_auth);
 	if (rx) {
 		if (!seg_rx_is_valid(rx, net_rx, hdr, seg_n)) {
 			return -EINVAL;
@@ -985,8 +1134,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_zero, rx->block);
-			return 0;
+				 net_rx->ctx.send_ttl, seq_auth, rx->block,
+				 rx->obo);
+			return -EALREADY;
 		}
 
 		/* We ignore instead of sending block ack 0 since the
@@ -1001,25 +1151,28 @@ static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx)
 	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,
-			 net_rx->ctx.send_ttl, seq_zero, 0);
-		return 0;
+			 net_rx->ctx.send_ttl, seq_auth, 0,
+			 net_rx->friend_match);
+		return -EMSGSIZE;
 	}
 
 	/* Look for free slot for a new RX session */
-	rx = seg_rx_alloc(net_rx, hdr, &seq_auth, seg_n);
+	rx = seg_rx_alloc(net_rx, hdr, seq_auth, seg_n);
 	if (!rx) {
 		/* Warn but don't cancel since the existing slots willl
 		 * eventually be freed up and we'll be able to process
 		 * this one.
 		 */
 		BT_WARN("No free slots for new incoming segmented messages");
-		return 0;
+		return -ENOMEM;
 	}
 
+	rx->obo = net_rx->friend_match;
+
 found_rx:
 	if (BIT(seg_o) & rx->block) {
 		BT_WARN("Received already received fragment");
-		return 0;
+		return -EALREADY;
 	}
 
 	/* All segments, except the last one, must either have 8 bytes of
@@ -1035,9 +1188,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_zero, 0);
+				 net_rx->ctx.send_ttl, seq_auth, 0, rx->obo);
 			seg_rx_reset(rx);
-			return 0;
+			return -EMSGSIZE;
 		}
 	} else {
 		if (buf->om_len != seg_len(rx->ctl)) {
@@ -1063,23 +1216,33 @@ static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx)
 	rx->block |= BIT(seg_o);
 
 	if (rx->block != BLOCK_COMPLETE(seg_n)) {
+		*pdu_type = BT_MESH_FRIEND_PDU_PARTIAL;
 		return 0;
 	}
 
 	BT_DBG("Complete SDU");
 
+	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);
+		/* Clear the segment's bit */
+		rx->block &= ~BIT(seg_o);
+		return -EINVAL;
+	}
+
+	*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,
-		 net_rx->ctx.send_ttl, seq_zero, rx->block);
+		 net_rx->ctx.send_ttl, seq_auth, rx->block, rx->obo);
 
 	if (net_rx->ctl) {
-		err = ctl_recv(net_rx, *hdr, rx->buf);
+		err = ctl_recv(net_rx, *hdr, rx->buf, seq_auth);
 	} else {
-		err = sdu_recv(net_rx, *hdr, MIC_SIZE(hdr),
-			       SZMIC(MIC_SIZE(hdr)), rx->buf);
+		err = sdu_recv(net_rx, *hdr, ASZMIC(hdr), rx->buf);
 	}
 
 	seg_rx_reset(rx);
@@ -1089,10 +1252,20 @@ static int trans_seg(struct os_mbuf *buf, struct bt_mesh_net_rx *net_rx)
 
 int bt_mesh_trans_recv(struct os_mbuf *buf, struct bt_mesh_net_rx *rx)
 {
+	u64_t seq_auth = TRANS_SEQ_AUTH_NVAL;
+	enum bt_mesh_friend_pdu_type pdu_type = BT_MESH_FRIEND_PDU_SINGLE;
+	struct net_buf_simple_state state;
 	int err;
 
-	BT_DBG("src 0x%04x dst 0x%04x seq 0x%08x", rx->ctx.addr, rx->dst,
-	       rx->seq);
+	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) {
+		rx->friend_match = bt_mesh_friend_match(rx->sub->net_idx,
+							rx->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);
 
 	/* Remove network headers */
 	net_buf_simple_pull(buf, BT_MESH_NET_HDR_LEN);
@@ -1104,16 +1277,28 @@ int bt_mesh_trans_recv(struct os_mbuf *buf, struct bt_mesh_net_rx *rx)
 	 * be encrypted using the Friend Credentials.
 	 */
 	if ((MYNEWT_VAL(BLE_MESH_LOW_POWER)) &&
-	    bt_mesh_lpn_established() &&
-	    (!bt_mesh_lpn_waiting_update() || !rx->ctx.friend_cred)) {
+	    bt_mesh_lpn_established() && rx->net_if == BT_MESH_NET_IF_ADV &&
+	    (!bt_mesh_lpn_waiting_update() || !rx->friend_cred)) {
 		BT_WARN("Ignoring unexpected message in Low Power mode");
 		return -EAGAIN;
 	}
 
+	/* Save the app-level state so the buffer can later be placed in
+	 * the Friend Queue.
+	 */
+	net_buf_simple_save(buf, &state);
+
 	if (SEG(buf->om_data)) {
-		err = trans_seg(buf, rx);
+		/* Segmented messages must match a local element or an
+		 * LPN of this Friend.
+		 */
+		if (!rx->local_match && !rx->friend_match) {
+			return 0;
+		}
+
+		err = trans_seg(buf, rx, &pdu_type, &seq_auth);
 	} else {
-		err = trans_unseg(buf, rx);
+		err = trans_unseg(buf, rx, &seq_auth);
 	}
 
 	/* Notify LPN state machine so a Friend Poll will be sent. If the
@@ -1122,12 +1307,31 @@ int bt_mesh_trans_recv(struct os_mbuf *buf, struct bt_mesh_net_rx *rx)
 	 * bt_mesh_lpn_waiting_update() function will return false:
 	 * we still need to go through the actual sending to the bearer and
 	 * wait for ReceiveDelay before transitioning to WAIT_UPDATE state.
+	 *
+	 * Another situation where we want to notify the LPN state machine
+	 * is if it's configured to use an automatic Friendship establishment
+	 * timer, in which case we want to reset the timer at this point.
+	 *
+	 * ENOENT is a special condition that's only used to indicate that
+	 * the Transport OpCode was invalid, in which case we should ignore
+	 * the PDU completely, as per MESH/NODE/FRND/LPN/BI-02-C.
 	 */
-	if ((MYNEWT_VAL(BLE_MESH_LOW_POWER)) &&
-	    bt_mesh_lpn_established() && bt_mesh_lpn_waiting_update()) {
+	if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && err != -ENOENT &&
+	    (bt_mesh_lpn_timer() ||
+	     (bt_mesh_lpn_established() && bt_mesh_lpn_waiting_update()))) {
 		bt_mesh_lpn_msg_received(rx);
 	}
 
+	net_buf_simple_restore(buf, &state);
+
+	if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && rx->friend_match && !err) {
+		if (seq_auth == TRANS_SEQ_AUTH_NVAL) {
+			bt_mesh_friend_enqueue_rx(rx, pdu_type, NULL, buf);
+		} else {
+			bt_mesh_friend_enqueue_rx(rx, pdu_type, &seq_auth, buf);
+		}
+	}
+
 	return err;
 }
 
@@ -1138,7 +1342,20 @@ void bt_mesh_rx_reset(void)
 	BT_DBG("");
 
 	for (i = 0; i < ARRAY_SIZE(seg_rx); i++) {
-		seg_rx[i].in_use = 0;
+		seg_rx_reset(&seg_rx[i]);
+		seg_rx[i].src = BT_MESH_ADDR_UNASSIGNED;
+		seg_rx[i].dst = BT_MESH_ADDR_UNASSIGNED;
+	}
+}
+
+void bt_mesh_tx_reset(void)
+{
+	int i;
+
+	BT_DBG("");
+
+	for (i = 0; i < ARRAY_SIZE(seg_tx); i++) {
+		seg_tx_reset(&seg_tx[i]);
 	}
 }
 
@@ -1160,3 +1377,9 @@ void bt_mesh_trans_init(void)
 		k_delayed_work_add_arg(&seg_rx[i].ack, &seg_rx[i]);
 	}
 }
+
+void bt_mesh_rpl_clear(void)
+{
+	BT_DBG("");
+	memset(bt_mesh.rpl, 0, sizeof(bt_mesh.rpl));
+}
diff --git a/net/nimble/host/mesh/src/transport.h b/net/nimble/host/mesh/src/transport.h
index 072085e61..d676f782e 100644
--- a/net/nimble/host/mesh/src/transport.h
+++ b/net/nimble/host/mesh/src/transport.h
@@ -9,6 +9,8 @@
 #include "syscfg/syscfg.h"
 #include "mesh/mesh.h"
 
+#define TRANS_SEQ_AUTH_NVAL 0xffffffffffffffff
+
 #define BT_MESH_TX_SEG_COUNT (MYNEWT_VAL(BLE_MESH_ADV_BUF_COUNT) - 3)
 #define BT_MESH_TX_SDU_MAX (BT_MESH_TX_SEG_COUNT * 12)
 
@@ -28,79 +30,71 @@
 #define TRANS_CTL_OP_FRIEND_SUB_CFM    0x09
 #define TRANS_CTL_OP_HEARTBEAT         0x0a
 
-struct bt_mesh_ctl_friend_poll
-{
-    u8_t fsn;
+struct bt_mesh_ctl_friend_poll {
+	u8_t  fsn;
 }__attribute__((__packed__));
 
-struct bt_mesh_ctl_friend_update
-{
-    u8_t flags;
-    u32_t iv_index;
-    u8_t md;
+struct bt_mesh_ctl_friend_update {
+	u8_t  flags;
+	u32_t iv_index;
+	u8_t  md;
 }__attribute__((__packed__));
 
-struct bt_mesh_ctl_friend_req
-{
-    u8_t criteria;
-    u8_t recv_delay;
-    u8_t poll_to[3];
-    u16_t prev_addr;
-    u8_t num_elem;
-    u16_t lpn_counter;
+struct bt_mesh_ctl_friend_req {
+	u8_t  criteria;
+	u8_t  recv_delay;
+	u8_t  poll_to[3];
+	u16_t prev_addr;
+	u8_t  num_elem;
+	u16_t lpn_counter;
 }__attribute__((__packed__));
 
-struct bt_mesh_ctl_friend_offer
-{
-    u8_t recv_win;
-    u8_t queue_size;
-    u8_t sub_list_size;
-    s8_t rssi;
-    u16_t frnd_counter;
+struct bt_mesh_ctl_friend_offer {
+	u8_t  recv_win;
+	u8_t  queue_size;
+	u8_t  sub_list_size;
+	s8_t  rssi;
+	u16_t frnd_counter;
 }__attribute__((__packed__));
 
-struct bt_mesh_ctl_friend_clear
-{
-    u16_t lpn_addr;
-    u16_t lpn_counter;
+struct bt_mesh_ctl_friend_clear {
+	u16_t lpn_addr;
+	u16_t lpn_counter;
 }__attribute__((__packed__));
 
-struct bt_mesh_ctl_friend_clear_confirm
-{
-    u16_t lpn_addr;
-    u16_t lpn_counter;
+struct bt_mesh_ctl_friend_clear_confirm {
+	u16_t lpn_addr;
+	u16_t lpn_counter;
 }__attribute__((__packed__));
 
-struct bt_mesh_ctl_friend_sub
-{
-    u8_t xact;
-    u16_t addr_list[5];
+#define BT_MESH_FRIEND_SUB_MIN_LEN (1 + 2)
+struct bt_mesh_ctl_friend_sub {
+	u8_t  xact;
+	u16_t addr_list[5];
 }__attribute__((__packed__));
 
-struct bt_mesh_ctl_friend_sub_confirm
-{
-    u8_t xact;
+struct bt_mesh_ctl_friend_sub_confirm {
+	u8_t  xact;
 }__attribute__((__packed__));
 
-struct bt_mesh_app_key *
-bt_mesh_app_key_find(u16_t app_idx);
+void bt_mesh_set_hb_sub_dst(u16_t addr);
+
+struct bt_mesh_app_key *bt_mesh_app_key_find(u16_t app_idx);
+
+bool bt_mesh_tx_in_progress(void);
 
-bool
-bt_mesh_tx_in_progress(void);
+void bt_mesh_rx_reset(void);
+void bt_mesh_tx_reset(void);
 
-void
-bt_mesh_rx_reset(void);
+int bt_mesh_ctl_send(struct bt_mesh_net_tx *tx, u8_t ctl_op, void *data,
+		     size_t data_len, u64_t *seq_auth,
+		     const struct bt_mesh_send_cb *cb, void *cb_data);
 
-int
-bt_mesh_ctl_send(struct bt_mesh_net_tx *tx, u8_t ctl_op, void *data,
-                 size_t data_len, bt_mesh_adv_func_t cb);
+int bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct os_mbuf *msg,
+		       const struct bt_mesh_send_cb *cb, void *cb_data);
 
-int
-bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct os_mbuf *msg,
-                   bt_mesh_cb_t cb, void *cb_data);
+int bt_mesh_trans_recv(struct os_mbuf *buf, struct bt_mesh_net_rx *rx);
 
-int
-bt_mesh_trans_recv(struct os_mbuf *buf, struct bt_mesh_net_rx *rx);
+void bt_mesh_trans_init(void);
 
-void
-bt_mesh_trans_init(void);
+void bt_mesh_rpl_clear(void);
diff --git a/net/nimble/host/mesh/syscfg.yml b/net/nimble/host/mesh/syscfg.yml
index 6a55f6ae9..7ed342ca2 100644
--- a/net/nimble/host/mesh/syscfg.yml
+++ b/net/nimble/host/mesh/syscfg.yml
@@ -44,12 +44,21 @@ syscfg.defs:
         value: 1
 
     BLE_MESH_GATT_PROXY:
-        descruption: >
+        description: >
             This option enables support for the Mesh GATT Proxy Service,
             i.e. the ability to act as a proxy between a Mesh GATT Client
             and a Mesh network.
         value: 1
-    
+
+    BLE_MESH_NODE_ID_TIMEOUT:
+        description: >
+            This option determines for how long the local node advertises
+            using Node Identity. The given value is in seconds. The
+            specification limits this to 60 seconds, and implies that to
+            be the appropriate value as well, so just leaving this as the
+            default is the safest option.
+        value: 60
+
     BLE_MESH_PROXY_FILTER_SIZE:
         descryption: >
             This option specifies how many Proxy Filter entries the local
@@ -143,8 +152,37 @@ syscfg.defs:
     BLE_MESH_LOW_POWER:
         description: >
            Enable this option to be able to act as a Low Power Node.
+        value: 0
+
+    BLE_MESH_LPN_ESTABLISHMENT:
+        description: >
+           Perform the Friendship establishment using low power, with
+           the help of a reduced scan duty cycle. The downside of this
+           is that the node may miss out on messages intended for it
+           until it has successfully set up Friendship with a Friend
+           node.
         value: 1
 
+    BLE_MESH_LPN_AUTO:
+        description: >
+           Automatically enable LPN functionality once provisioned and start
+           looking for Friend nodes. If this option is disabled LPN mode
+           needs to be manually enabled by calling bt_mesh_lpn_set(true).
+           node.
+        value: 1
+
+    BLE_MESH_LPN_AUTO_TIMEOUT:
+        description: >
+           Time in seconds from the last received message, that the node
+           will wait before starting to look for Friend nodes.
+        value: 15
+
+    BLE_MESH_LPN_RETRY_TIMEOUT:
+        description: >
+           Time in seconds between Friend Requests, if a previous Friend
+           Request did not receive any acceptable Friend Offers.
+        value: 8
+
     BLE_MESH_LPN_RSSI_FACTOR:
         description: >
             The contribution of the RSSI measured by the Friend node used
@@ -171,7 +209,7 @@ syscfg.defs:
             sending a request and listening for a response. This delay
             allows the Friend node time to prepare the response. The value
             is in units of milliseconds.
-        value: 20
+        value: 100
 
     BLE_MESH_LPN_POLL_TIMEOUT:
         description: >
@@ -179,8 +217,18 @@ syscfg.defs:
             consecutive requests sent by the Low Power node. If no
             requests are received by the Friend node before the
             PollTimeout timer expires, then the friendship is considered
-            terminated. The value is in units of 100 milliseconds.
-        value: 100
+            terminated. The value is in units of 100 milliseconds, so e.g.
+            a value of 300 means 30 seconds.
+        value: 300
+
+    BLE_MESH_LPN_INIT_POLL_TIMEOUT:
+        description: >
+            The initial value of the PollTimeout timer when Friendship
+            gets established for the first time. After this the timeout
+            will gradually grow toward the actual PollTimeout, doubling
+            in value for each iteration. The value is in units of 100
+            milliseconds, so e.g. a value of 300 means 3 seconds.
+        value: MYNEWT_VAL_BLE_MESH_LPN_POLL_TIMEOUT
         
     BLE_MESH_LPN_SCAN_LATENCY:
         description: >
@@ -197,7 +245,7 @@ syscfg.defs:
     BLE_MESH_FRIEND:
         description: >
             Enable this option to be able to act as a Friend Node.
-        value: 1
+        value: 0
 
     BLE_MESH_FRIEND_RECV_WIN:
         description: >
@@ -206,21 +254,46 @@ syscfg.defs:
         
     BLE_MESH_FRIEND_QUEUE_SIZE:
         description: >
-            Queue Size available on the Friend node.
+            Minimum number of buffers available to be stored for each 
+            local Friend Queue.
         value: 16
         
     BLE_MESH_FRIEND_SUB_LIST_SIZE:
         description: >
             Size of the Subscription List that can be supported by a
             Friend node for a Low Power node.
-        value: 16
+        value: 3
         
     BLE_MESH_FRIEND_LPN_COUNT:
         description: >
             Number of Low Power Nodes the Friend can have a Friendship
             with simultaneously.
+        value: 2
+
+    BLE_MESH_FRIEND_SEG_RX:
+        description: >
+            Number of incomplete segment lists that we track for each LPN
+            that we are Friends for. In other words, this determines how
+            many elements we can simultaneously be receiving segmented
+            messages from when the messages are going into the Friend queue.
         value: 1
 
+    BLE_MESH_CFG_CLI:
+        description: >
+            Enable support for the configuration client model.
+        value: 0
+
+    BLE_MESH_HEALTH_CLI:
+        description: >
+            Enable support for the health client model.
+        value: 0
+
+    BLE_MESH_SHELL:
+        description: >
+            Activate shell module that provides Bluetooth Mesh commands to
+            the console.
+        value: 0
+
     BLE_MESH_DEBUG:
         description: >
             Use this option to enable debug logs for the Bluetooth
@@ -302,7 +375,11 @@ syscfg.defs:
         description: >
             Device UUID
         value: ((uint8_t[16]){0x11, 0x22, 0})
-        
+
+syscfg.vals.BLE_MESH_SHELL:
+    BLE_MESH_CFG_CLI: 1
+    BLE_MESH_HEALTH_CLI: 1
+    BLE_MESH_IV_UPDATE_TEST: 1
 
 syscfg.vals.BLE_MESH_GATT_PROXY:
     BLE_MESH_PROXY: 1
diff --git a/net/nimble/host/syscfg.yml b/net/nimble/host/syscfg.yml
index 464fcd330..5559dc930 100644
--- a/net/nimble/host/syscfg.yml
+++ b/net/nimble/host/syscfg.yml
@@ -369,7 +369,9 @@ syscfg.defs:
 
     BLE_MESH:
         description: >
-            Enable Bluetooth Mesh	
+            This option enables Bluetooth Mesh support. The specific
+            features that are available may depend on other features
+            that have been enabled in the stack, such as GATT support.
         value: 0
 
 syscfg.vals.BLE_MESH:
diff --git a/sys/shell/include/shell/shell.h b/sys/shell/include/shell/shell.h
index cb1d9e522..065b7e458 100644
--- a/sys/shell/include/shell/shell.h
+++ b/sys/shell/include/shell/shell.h
@@ -24,6 +24,8 @@
 extern "C" {
 #endif
 
+#include "syscfg/syscfg.h"
+
 struct os_eventq;
 
 /** @brief Callback called when command is entered.


 

----------------------------------------------------------------
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