All of lore.kernel.org
 help / color / mirror / Atom feed
From: Brian Gix <brian.gix@gmail.com>
To: linux-bluetooth@vger.kernel.org
Cc: Brian Gix <brian.gix@intel.com>
Subject: [PATCH BlueZ 02/12] mesh: Add Remote Provisioning
Date: Thu, 19 Jan 2023 15:57:45 -0800	[thread overview]
Message-ID: <20230119235755.46002-3-brian.gix@gmail.com> (raw)
In-Reply-To: <20230119235755.46002-1-brian.gix@gmail.com>

From: Brian Gix <brian.gix@intel.com>

Add Remote Provisioning Server
Add Remote Provisioning Client
Remove local scanning/provisioning
Add delete-all dev key function
Add NPPI procedures
---
 Makefile.mesh           |   1 +
 mesh/cfgmod-server.c    |   2 +-
 mesh/keyring.c          |  29 +-
 mesh/keyring.h          |   1 +
 mesh/manager.c          | 533 ++++++++++++++++++-----
 mesh/mesh-config-json.c | 380 +++++++++++------
 mesh/mesh-config.h      |   6 +-
 mesh/model.c            |  27 +-
 mesh/node.c             | 255 +++++++++--
 mesh/node.h             |   3 +
 mesh/pb-adv.c           |   4 +-
 mesh/pb-adv.h           |   2 +-
 mesh/prov-acceptor.c    |  87 ++--
 mesh/prov-initiator.c   | 266 +++++++++++-
 mesh/prov.h             |   4 +-
 mesh/provision.h        |  23 +-
 mesh/remprv-server.c    | 908 ++++++++++++++++++++++++++++++++++++++++
 mesh/remprv.h           |  78 ++++
 18 files changed, 2244 insertions(+), 365 deletions(-)
 create mode 100644 mesh/remprv-server.c
 create mode 100644 mesh/remprv.h

diff --git a/Makefile.mesh b/Makefile.mesh
index 3047f362b..e18a169eb 100644
--- a/Makefile.mesh
+++ b/Makefile.mesh
@@ -26,6 +26,7 @@ mesh_sources = mesh/mesh.h mesh/mesh.c \
 				mesh/provision.h mesh/prov.h \
 				mesh/model.h mesh/model.c \
 				mesh/cfgmod.h mesh/cfgmod-server.c \
+				mesh/remprv.h mesh/remprv-server.c \
 				mesh/mesh-config.h mesh/mesh-config-json.c \
 				mesh/util.h mesh/util.c \
 				mesh/dbus.h mesh/dbus.c \
diff --git a/mesh/cfgmod-server.c b/mesh/cfgmod-server.c
index be90ef8c5..3d7efc44b 100644
--- a/mesh/cfgmod-server.c
+++ b/mesh/cfgmod-server.c
@@ -30,8 +30,8 @@
 		(SET_ID(SIG_VENDOR, l_get_le16(pkt))))
 
 /* Supported composition pages, sorted high to low */
-/* Only page 0 is currently supported */
 static const uint8_t supported_pages[] = {
+	128,
 	0
 };
 
diff --git a/mesh/keyring.c b/mesh/keyring.c
index 995a4b88f..894fb14fa 100644
--- a/mesh/keyring.c
+++ b/mesh/keyring.c
@@ -30,9 +30,9 @@
 #include "mesh/node.h"
 #include "mesh/keyring.h"
 
-const char *dev_key_dir = "/dev_keys";
-const char *app_key_dir = "/app_keys";
-const char *net_key_dir = "/net_keys";
+static const char *dev_key_dir = "/dev_keys";
+static const char *app_key_dir = "/app_keys";
+static const char *net_key_dir = "/net_keys";
 
 static int open_key_file(struct mesh_node *node, const char *key_dir,
 							uint16_t idx, int flags)
@@ -295,6 +295,7 @@ bool keyring_get_remote_dev_key(struct mesh_node *node, uint16_t unicast,
 		close(fd);
 	}
 
+
 	return result;
 }
 
@@ -371,6 +372,28 @@ bool keyring_del_remote_dev_key(struct mesh_node *node, uint16_t unicast,
 	return true;
 }
 
+bool keyring_del_remote_dev_key_all(struct mesh_node *node, uint16_t unicast)
+{
+	uint8_t dev_key[16];
+	uint8_t test_key[16];
+	uint8_t cnt = 1;
+
+	if (!keyring_get_remote_dev_key(node, unicast, dev_key))
+		return false;
+
+	while (keyring_get_remote_dev_key(node, unicast + cnt, test_key)) {
+		if (memcmp(dev_key, test_key, sizeof(dev_key)))
+			break;
+
+		cnt++;
+	}
+
+	if (cnt > 1)
+		return keyring_del_remote_dev_key(node, unicast + 1, cnt - 1);
+
+	return true;
+}
+
 static DIR *open_key_dir(const char *node_path, const char *key_dir_name)
 {
 	char dir_path[PATH_MAX];
diff --git a/mesh/keyring.h b/mesh/keyring.h
index ecf62cbc1..efc499ac2 100644
--- a/mesh/keyring.h
+++ b/mesh/keyring.h
@@ -39,5 +39,6 @@ bool keyring_put_remote_dev_key(struct mesh_node *node, uint16_t unicast,
 					uint8_t count, uint8_t dev_key[16]);
 bool keyring_del_remote_dev_key(struct mesh_node *node, uint16_t unicast,
 								uint8_t count);
+bool keyring_del_remote_dev_key_all(struct mesh_node *node, uint16_t unicast);
 bool keyring_build_export_keys_reply(struct mesh_node *node,
 					struct l_dbus_message_builder *builder);
diff --git a/mesh/manager.c b/mesh/manager.c
index e66b1a45b..0730fa266 100644
--- a/mesh/manager.c
+++ b/mesh/manager.c
@@ -21,75 +21,137 @@
 #include "mesh/mesh.h"
 #include "mesh/mesh-io.h"
 #include "mesh/node.h"
+#include "mesh/model.h"
 #include "mesh/net.h"
 #include "mesh/keyring.h"
 #include "mesh/agent.h"
 #include "mesh/provision.h"
+#include "mesh/prov.h"
+#include "mesh/remprv.h"
 #include "mesh/manager.h"
 
-struct add_data{
+struct prov_remote_data {
 	struct l_dbus_message *msg;
 	struct mesh_agent *agent;
 	struct mesh_node *node;
 	uint32_t disc_watch;
+	uint16_t original;
 	uint16_t primary;
 	uint16_t net_idx;
+	uint8_t transport;
 	uint8_t num_ele;
 	uint8_t uuid[16];
 };
 
-static int8_t scan_rssi;
-static uint8_t scan_uuid[16];
-static struct mesh_node *scan_node;
-static struct l_timeout *scan_timeout;
-static struct add_data *add_pending;
+struct scan_req {
+	struct mesh_node *node;
+	struct l_timeout *timeout;
+	uint16_t server;
+	uint16_t net_idx;
+	uint8_t uuid[16];
+	int8_t rssi;
+	bool ext;
+};
+
+static struct l_queue *scans;
+static struct prov_remote_data *prov_pending;
 static const uint8_t prvb[2] = {MESH_AD_TYPE_BEACON, 0x00};
 
+static bool by_scan(const void *a, const void *b)
+{
+	return a == b;
+}
+
+static bool by_node(const void *a, const void *b)
+{
+	const struct scan_req *req = a;
+	const struct mesh_node *node = b;
+
+	return req->node == node;
+}
+
+static bool by_node_svr(const void *a, const void *b)
+{
+	const struct scan_req *req = a;
+	const struct scan_req *test = b;
+
+	return req->node == test->node && req->server == test->server;
+}
+
 static void scan_cancel(struct l_timeout *timeout, void *user_data)
 {
-	struct mesh_node *node = user_data;
+	struct scan_req *req = user_data;
 	struct mesh_io *io;
 	struct mesh_net *net;
+	uint8_t msg[4];
+	int n;
 
 	l_debug("");
 
-	if (scan_timeout)
-		l_timeout_remove(scan_timeout);
+	req = l_queue_remove_if(scans, by_scan, req);
+
+	if (!req)
+		return;
+
+	l_timeout_remove(req->timeout);
+
+	if (req->server) {
+		n = mesh_model_opcode_set(OP_REM_PROV_SCAN_STOP, msg);
+		mesh_model_send(req->node, 0, req->server, APP_IDX_DEV_REMOTE,
+						req->net_idx, DEFAULT_TTL,
+						true, n, msg);
+	} else {
+		net = node_get_net(req->node);
+		io = mesh_net_get_io(net);
+		mesh_io_deregister_recv_cb(io, prvb, sizeof(prvb));
+	}
 
-	net = node_get_net(node);
-	io = mesh_net_get_io(net);
-	mesh_io_deregister_recv_cb(io, prvb, sizeof(prvb));
-	scan_node = NULL;
-	scan_timeout = NULL;
+	initiator_scan_unreg(req->node);
+	l_free(req);
 }
 
 static void free_pending_add_call()
 {
-	if (!add_pending)
+	if (!prov_pending)
 		return;
 
-	if (add_pending->disc_watch)
+	if (prov_pending->disc_watch)
 		l_dbus_remove_watch(dbus_get_bus(),
-						add_pending->disc_watch);
+						prov_pending->disc_watch);
 
-	if (add_pending->msg)
-		l_dbus_message_unref(add_pending->msg);
+	if (prov_pending->msg)
+		l_dbus_message_unref(prov_pending->msg);
 
-	l_free(add_pending);
-	add_pending = NULL;
+	l_free(prov_pending);
+	prov_pending = NULL;
 }
 
 static void prov_disc_cb(struct l_dbus *bus, void *user_data)
 {
-	if (!add_pending)
+	if (!prov_pending)
 		return;
 
-	initiator_cancel(add_pending);
-	add_pending->disc_watch = 0;
+	initiator_cancel(prov_pending);
+	prov_pending->disc_watch = 0;
 
 	free_pending_add_call();
 }
 
+static void append_dict_entry_basic(struct l_dbus_message_builder *builder,
+					const char *key, const char *signature,
+					const void *data)
+{
+	if (!builder)
+		return;
+
+	l_dbus_message_builder_enter_dict(builder, "sv");
+	l_dbus_message_builder_append_basic(builder, 's', key);
+	l_dbus_message_builder_enter_variant(builder, signature);
+	l_dbus_message_builder_append_basic(builder, signature[0], data);
+	l_dbus_message_builder_leave_variant(builder);
+	l_dbus_message_builder_leave_dict(builder);
+}
+
 static void send_add_failed(const char *owner, const char *path,
 							uint8_t status)
 {
@@ -102,7 +164,7 @@ static void send_add_failed(const char *owner, const char *path,
 						"AddNodeFailed");
 
 	builder = l_dbus_message_builder_new(msg);
-	dbus_append_byte_array(builder, add_pending->uuid, 16);
+	dbus_append_byte_array(builder, prov_pending->uuid, 16);
 	l_dbus_message_builder_append_basic(builder, 's',
 						mesh_prov_status_str(status));
 	l_dbus_message_builder_finalize(builder);
@@ -115,14 +177,14 @@ static void send_add_failed(const char *owner, const char *path,
 static bool add_cmplt(void *user_data, uint8_t status,
 					struct mesh_prov_node_info *info)
 {
-	struct add_data *pending = user_data;
+	struct prov_remote_data *pending = user_data;
 	struct mesh_node *node = pending->node;
 	struct l_dbus *dbus = dbus_get_bus();
 	struct l_dbus_message_builder *builder;
 	struct l_dbus_message *msg;
 	bool result;
 
-	if (pending != add_pending)
+	if (pending != prov_pending)
 		return false;
 
 	if (status != PROV_ERR_SUCCESS) {
@@ -131,7 +193,12 @@ static bool add_cmplt(void *user_data, uint8_t status,
 		return false;
 	}
 
-	result = keyring_put_remote_dev_key(add_pending->node, info->unicast,
+	/* If Unicast address changing, delete old dev key */
+	if (pending->transport == PB_NPPI_01)
+		keyring_del_remote_dev_key_all(pending->node,
+							pending->original);
+
+	result = keyring_put_remote_dev_key(pending->node, info->unicast,
 					info->num_ele, info->device_key);
 
 	if (!result) {
@@ -140,13 +207,29 @@ static bool add_cmplt(void *user_data, uint8_t status,
 		return false;
 	}
 
-	msg = l_dbus_message_new_method_call(dbus, node_get_owner(node),
+	if (pending->transport > PB_NPPI_02)
+		msg = l_dbus_message_new_method_call(dbus, node_get_owner(node),
 						node_get_app_path(node),
 						MESH_PROVISIONER_INTERFACE,
 						"AddNodeComplete");
+	else
+		msg = l_dbus_message_new_method_call(dbus, node_get_owner(node),
+						node_get_app_path(node),
+						MESH_PROVISIONER_INTERFACE,
+						"ReprovComplete");
 
 	builder = l_dbus_message_builder_new(msg);
-	dbus_append_byte_array(builder, add_pending->uuid, 16);
+
+	if (pending->transport > PB_NPPI_02)
+		dbus_append_byte_array(builder, pending->uuid, 16);
+	else {
+		uint8_t nppi = (uint8_t) pending->transport;
+
+		l_dbus_message_builder_append_basic(builder, 'q',
+							&pending->original);
+		l_dbus_message_builder_append_basic(builder, 'y', &nppi);
+	}
+
 	l_dbus_message_builder_append_basic(builder, 'q', &info->unicast);
 	l_dbus_message_builder_append_basic(builder, 'y', &info->num_ele);
 	l_dbus_message_builder_finalize(builder);
@@ -161,47 +244,66 @@ static bool add_cmplt(void *user_data, uint8_t status,
 
 static void mgr_prov_data (struct l_dbus_message *reply, void *user_data)
 {
-	struct add_data *pending = user_data;
+	struct prov_remote_data *pending = user_data;
 	uint16_t net_idx;
 	uint16_t primary;
 
-	if (pending != add_pending)
+	if (pending != prov_pending)
 		return;
 
 	if (l_dbus_message_is_error(reply))
 		return;
 
-	if (!l_dbus_message_get_arguments(reply, "qq", &net_idx, &primary))
+	if (pending->transport == PB_NPPI_01) {
+		/* If performing NPPI, we only get new primary unicast here */
+		if (!l_dbus_message_get_arguments(reply, "q", &primary))
+			return;
+
+		net_idx = pending->net_idx;
+
+	} else if (!l_dbus_message_get_arguments(reply, "qq", &net_idx,
+								&primary))
 		return;
 
-	add_pending->primary = primary;
-	add_pending->net_idx = net_idx;
-	initiator_prov_data(net_idx, primary, add_pending);
+	pending->primary = primary;
+	pending->net_idx = net_idx;
+	initiator_prov_data(net_idx, primary, pending);
 }
 
 static bool add_data_get(void *user_data, uint8_t num_ele)
 {
-	struct add_data *pending = user_data;
+	struct prov_remote_data *pending = user_data;
 	struct l_dbus_message *msg;
 	struct l_dbus *dbus;
 	const char *app_path;
 	const char *sender;
 
-	if (pending != add_pending)
+	if (pending != prov_pending)
 		return false;
 
 	dbus = dbus_get_bus();
-	app_path = node_get_app_path(add_pending->node);
-	sender = node_get_owner(add_pending->node);
+	app_path = node_get_app_path(pending->node);
+	sender = node_get_owner(pending->node);
 
-	msg = l_dbus_message_new_method_call(dbus, sender, app_path,
+	if (pending->transport > PB_NPPI_02) {
+		msg = l_dbus_message_new_method_call(dbus, sender, app_path,
 						MESH_PROVISIONER_INTERFACE,
 						"RequestProvData");
 
-	l_dbus_message_set_arguments(msg, "y", num_ele);
-	l_dbus_send_with_reply(dbus, msg, mgr_prov_data, add_pending, NULL);
+		l_dbus_message_set_arguments(msg, "y", num_ele);
+	} else if (pending->transport == PB_NPPI_01) {
+		msg = l_dbus_message_new_method_call(dbus, sender, app_path,
+						MESH_PROVISIONER_INTERFACE,
+						"RequestReprovData");
+
+		l_dbus_message_set_arguments(msg, "qy", pending->original,
+								num_ele);
+	} else
+		return false;
 
-	add_pending->num_ele = num_ele;
+	l_dbus_send_with_reply(dbus, msg, mgr_prov_data, pending, NULL);
+
+	pending->num_ele = num_ele;
 
 	return true;
 }
@@ -213,15 +315,95 @@ static void add_start(void *user_data, int err)
 	l_debug("Start callback");
 
 	if (err == MESH_ERROR_NONE)
-		reply = l_dbus_message_new_method_return(add_pending->msg);
+		reply = l_dbus_message_new_method_return(prov_pending->msg);
 	else
-		reply = dbus_error(add_pending->msg, MESH_ERROR_FAILED,
+		reply = dbus_error(prov_pending->msg, MESH_ERROR_FAILED,
 				"Failed to start provisioning initiator");
 
 	l_dbus_send(dbus_get_bus(), reply);
-	l_dbus_message_unref(add_pending->msg);
+	l_dbus_message_unref(prov_pending->msg);
+
+	prov_pending->msg = NULL;
+}
+
+static struct l_dbus_message *reprovision_call(struct l_dbus *dbus,
+						struct l_dbus_message *msg,
+						void *user_data)
+{
+	struct mesh_node *node = user_data;
+	struct l_dbus_message_iter options, var;
+	struct l_dbus_message *reply;
+	struct mesh_net *net = node_get_net(node);
+	const char *key;
+	uint16_t subidx;
+	uint16_t server = 0;
+	uint8_t nppi = 0;
+
+	l_debug("Reprovision request");
+
+	if (!l_dbus_message_get_arguments(msg, "qa{sv}", &server, &options))
+		return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL);
+
+	if (!IS_UNICAST(server))
+		return dbus_error(msg, MESH_ERROR_INVALID_ARGS, "Bad Unicast");
+
+	/* Default to nodes primary subnet index */
+	subidx = mesh_net_get_primary_idx(net);
+
+	/* Get Provisioning Options */
+	while (l_dbus_message_iter_next_entry(&options, &key, &var)) {
+		bool failed = true;
+
+		if (!strcmp(key, "NPPI")) {
+			if (l_dbus_message_iter_get_variant(&var, "y", &nppi)) {
+				if (nppi <= 2)
+					failed = false;
+			}
+		} else if (!strcmp(key, "Subnet")) {
+			if (l_dbus_message_iter_get_variant(&var, "q",
+								&subidx)) {
+				if (subidx <= MAX_KEY_IDX)
+					failed = false;
+			}
+		}
+
+		if (failed)
+			return dbus_error(msg, MESH_ERROR_INVALID_ARGS,
+							"Invalid options");
+	}
+
+	/* AddNode cancels all outstanding Scanning from node */
+	manager_scan_cancel(node);
 
-	add_pending->msg = NULL;
+	/* Invoke Prov Initiator */
+	prov_pending = l_new(struct prov_remote_data, 1);
+
+	prov_pending->transport = nppi;
+	prov_pending->node = node;
+	prov_pending->original = server;
+	prov_pending->agent = node_get_agent(node);
+
+	if (!node_is_provisioner(node) || (prov_pending->agent == NULL)) {
+		reply = dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED,
+							"Missing Interfaces");
+		goto fail;
+	}
+
+	prov_pending->msg = l_dbus_message_ref(msg);
+	initiator_start(prov_pending->transport, server, subidx, NULL, 99, 60,
+					prov_pending->agent, add_start,
+					add_data_get, add_cmplt, node,
+					prov_pending);
+
+	prov_pending->disc_watch = l_dbus_add_disconnect_watch(dbus,
+						node_get_owner(node),
+						prov_disc_cb, NULL, NULL);
+
+	return NULL;
+fail:
+	l_free(prov_pending);
+	prov_pending = NULL;
+	return reply;
 }
 
 static struct l_dbus_message *add_node_call(struct l_dbus *dbus,
@@ -229,55 +411,101 @@ static struct l_dbus_message *add_node_call(struct l_dbus *dbus,
 						void *user_data)
 {
 	struct mesh_node *node = user_data;
-	struct l_dbus_message_iter iter_uuid, options;
+	struct l_dbus_message_iter iter_uuid, options, var;
 	struct l_dbus_message *reply;
+	struct mesh_net *net = node_get_net(node);
+	const char *key;
 	uint8_t *uuid;
-	uint32_t n = 22;
+	uint32_t n = 0;
+	uint16_t subidx;
+	uint16_t sec = 60;
+	uint16_t server = 0;
 
 	l_debug("AddNode request");
 
 	if (!l_dbus_message_get_arguments(msg, "aya{sv}", &iter_uuid, &options))
 		return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL);
 
-	if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, &uuid, &n)
-								|| n != 16)
+	if (!l_dbus_message_iter_get_fixed_array(&iter_uuid, &uuid, &n) ||
+									n != 16)
 		return dbus_error(msg, MESH_ERROR_INVALID_ARGS,
 							"Bad device UUID");
 
-	/* Allow AddNode to cancel Scanning if from the same node */
-	if (scan_node) {
-		if (scan_node != node)
-			return dbus_error(msg, MESH_ERROR_BUSY, NULL);
+	/* Default to nodes primary subnet index */
+	subidx = mesh_net_get_primary_idx(net);
 
-		scan_cancel(NULL, node);
+	/* Get Provisioning Options */
+	while (l_dbus_message_iter_next_entry(&options, &key, &var)) {
+		bool failed = true;
+
+		if (!strcmp(key, "Seconds")) {
+			if (l_dbus_message_iter_get_variant(&var, "q", &sec))
+				failed = false;
+		} else if (!strcmp(key, "Server")) {
+			if (l_dbus_message_iter_get_variant(&var, "q",
+								&server)) {
+				if (server < 0x8000)
+					failed = false;
+			}
+		} else if (!strcmp(key, "Subnet")) {
+			if (l_dbus_message_iter_get_variant(&var, "q",
+								&subidx)) {
+				if (subidx <= MAX_KEY_IDX)
+					failed = false;
+			}
+		}
+
+		if (failed)
+			return dbus_error(msg, MESH_ERROR_INVALID_ARGS,
+							"Invalid options");
 	}
 
+	/* Device Key update/Composition update requires remote server */
+	if (!n && !server)
+		return dbus_error(msg, MESH_ERROR_INVALID_ARGS,
+							"Invalid options");
+
+	/* If no server specified, use local */
+	if (!server)
+		server = node_get_primary(node);
+
+	/* AddNode cancels all outstanding Scanning from node */
+	manager_scan_cancel(node);
+
 	/* Invoke Prov Initiator */
-	add_pending = l_new(struct add_data, 1);
-	memcpy(add_pending->uuid, uuid, 16);
-	add_pending->node = node;
-	add_pending->agent = node_get_agent(node);
+	prov_pending = l_new(struct prov_remote_data, 1);
+
+	if (n)
+		memcpy(prov_pending->uuid, uuid, 16);
+	else
+		uuid = NULL;
 
-	if (!node_is_provisioner(node) || (add_pending->agent == NULL)) {
+	prov_pending->transport = PB_ADV;
+	prov_pending->node = node;
+	prov_pending->agent = node_get_agent(node);
+
+	if (!node_is_provisioner(node) || (prov_pending->agent == NULL)) {
 		l_debug("Provisioner: %d", node_is_provisioner(node));
-		l_debug("Agent: %p", add_pending->agent);
+		l_debug("Agent: %p", prov_pending->agent);
 		reply = dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED,
 							"Missing Interfaces");
 		goto fail;
 	}
 
-	add_pending->msg = l_dbus_message_ref(msg);
-	initiator_start(PB_ADV, uuid, 99, 60, add_pending->agent, add_start,
-				add_data_get, add_cmplt, node, add_pending);
+	prov_pending->msg = l_dbus_message_ref(msg);
+	initiator_start(PB_ADV, server, subidx, uuid, 99, sec,
+					prov_pending->agent, add_start,
+					add_data_get, add_cmplt, node,
+					prov_pending);
 
-	add_pending->disc_watch = l_dbus_add_disconnect_watch(dbus,
+	prov_pending->disc_watch = l_dbus_add_disconnect_watch(dbus,
 						node_get_owner(node),
 						prov_disc_cb, NULL, NULL);
 
 	return NULL;
 fail:
-	l_free(add_pending);
-	add_pending = NULL;
+	l_free(prov_pending);
+	prov_pending = NULL;
 	return reply;
 }
 
@@ -337,38 +565,50 @@ static struct l_dbus_message *delete_node_call(struct l_dbus *dbus,
 	return l_dbus_message_new_method_return(msg);
 }
 
-static void prov_beacon_recv(void *user_data, struct mesh_io_recv_info *info,
+static void manager_scan_result(void *user_data, uint16_t server, bool ext,
 					const uint8_t *data, uint16_t len)
 {
-	struct mesh_node *node = user_data;
+	struct scan_req node_svr = {
+		.node = user_data,
+		.server = server,
+	};
+	struct scan_req *req;
 	struct l_dbus_message_builder *builder;
 	struct l_dbus_message *msg;
 	struct l_dbus *dbus;
 	int16_t rssi;
 
-	if (scan_node != node || len < sizeof(scan_uuid) + 2 || data[1] != 0x00)
+	l_debug("scan_result %4.4x %p", server, user_data);
+	req = l_queue_find(scans, by_node_svr, &node_svr);
+	if (!req) {
+		l_debug("No scan_result req");
 		return;
+	}
 
-	if (!memcmp(data + 2, scan_uuid, sizeof(scan_uuid))) {
-		if (info->rssi <= scan_rssi)
+	/* Filter repeats with weaker signal */
+	if (!memcmp(data + 1, req->uuid, sizeof(req->uuid))) {
+		if (!ext && ((int8_t) data[0] <= req->rssi)) {
+			l_debug("Already Seen");
 			return;
+		}
 	}
 
-	memcpy(scan_uuid, data + 2, sizeof(scan_uuid));
-	scan_rssi = info->rssi;
-	rssi = info->rssi;
+	if (!ext && ((int8_t) data[0] > req->rssi))
+		req->rssi = (int8_t) data[0];
 
+	rssi = req->rssi;
+	memcpy(req->uuid, data + 1, sizeof(req->uuid));
 	dbus = dbus_get_bus();
-	msg = l_dbus_message_new_method_call(dbus, node_get_owner(node),
-						node_get_app_path(node),
+	msg = l_dbus_message_new_method_call(dbus, node_get_owner(req->node),
+						node_get_app_path(req->node),
 						MESH_PROVISIONER_INTERFACE,
 						"ScanResult");
 
 	builder = l_dbus_message_builder_new(msg);
 	l_dbus_message_builder_append_basic(builder, 'n', &rssi);
-	dbus_append_byte_array(builder, data + 2, len -2);
+	dbus_append_byte_array(builder, data + 1, len - 1);
 	l_dbus_message_builder_enter_array(builder, "{sv}");
-	/* TODO: populate with options when defined */
+	append_dict_entry_basic(builder, "Server", "q", &server);
 	l_dbus_message_builder_leave_array(builder);
 	l_dbus_message_builder_finalize(builder);
 	l_dbus_message_builder_destroy(builder);
@@ -380,27 +620,71 @@ static struct l_dbus_message *start_scan_call(struct l_dbus *dbus,
 						struct l_dbus_message *msg,
 						void *user_data)
 {
-	struct mesh_node *node = user_data;
-	uint16_t duration = 0;
-	struct mesh_io *io;
+	struct scan_req new_req = {
+		.node = user_data,
+		.server = 0,
+		.timeout = NULL,
+		.ext = false,
+	};
+	struct scan_req *req;
 	struct mesh_net *net;
+	uint8_t *uuid, *ext = NULL;
+	uint8_t scan_req[21];
+	int n;
+	uint32_t ext_len;
+	uint32_t flen = 0;
+	uint16_t sec = 60;
 	const char *key;
 	struct l_dbus_message_iter options, var;
 	const char *sender = l_dbus_message_get_sender(msg);
 
-	if (strcmp(sender, node_get_owner(node)))
+	if (strcmp(sender, node_get_owner(new_req.node)))
 		return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL);
 
 	if (!l_dbus_message_get_arguments(msg, "a{sv}", &options))
 		return dbus_error(msg, MESH_ERROR_INVALID_ARGS, NULL);
 
+	if (!node_is_provisioner(new_req.node))
+		return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL);
+
+	net = node_get_net(new_req.node);
+	new_req.net_idx = mesh_net_get_primary_idx(net);
+	memset(new_req.uuid, 0, sizeof(new_req.uuid));
+
 	while (l_dbus_message_iter_next_entry(&options, &key, &var)) {
 		bool failed = true;
 
 		if (!strcmp(key, "Seconds")) {
-			if (l_dbus_message_iter_get_variant(&var, "q",
-							    &duration)) {
+			if (l_dbus_message_iter_get_variant(&var, "q", &sec))
 				failed = false;
+		} else if (!strcmp(key, "Subnet")) {
+			if (l_dbus_message_iter_get_variant(&var, "q",
+							&new_req.net_idx)) {
+				if (new_req.net_idx <= MAX_KEY_IDX)
+					failed = false;
+			}
+		} else if (!strcmp(key, "Server")) {
+			if (l_dbus_message_iter_get_variant(&var, "q",
+							&new_req.server)) {
+				if (new_req.server < 0x8000)
+					failed = false;
+			}
+		} else if (!strcmp(key, "Filter")) {
+			if (l_dbus_message_iter_get_variant(&var, "ay", &var)) {
+				if (l_dbus_message_iter_get_fixed_array(&var,
+								&uuid, &flen)) {
+					if (flen == 16) {
+						memcpy(new_req.uuid, uuid,
+									flen);
+						failed = false;
+					}
+				}
+			}
+		} else if (!strcmp(key, "Extended")) {
+			if (l_dbus_message_iter_get_variant(&var, "ay", &var)) {
+				if (l_dbus_message_iter_get_fixed_array(&var,
+								&ext, &ext_len))
+					failed = false;
 			}
 		}
 
@@ -409,27 +693,51 @@ static struct l_dbus_message *start_scan_call(struct l_dbus *dbus,
 							"Invalid options");
 	}
 
-	if (scan_node && scan_node != node)
-		return dbus_error(msg, MESH_ERROR_BUSY, NULL);
+	if (!scans)
+		scans = l_queue_new();
 
-	if (!node_is_provisioner(node))
-		return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL);
+	if (new_req.server) {
+		if (!sec || sec > 60)
+			return dbus_error(msg, MESH_ERROR_INVALID_ARGS,
+							"Invalid options");
+	} else {
+		new_req.server = node_get_primary(new_req.node);
+		if (!sec || sec > 60)
+			sec = 60;
+	}
+
+	req = l_queue_remove_if(scans, by_node_svr, &new_req);
+
+	if (!req)
+		req = l_malloc(sizeof(new_req));
+
+	if (req->timeout) {
+		l_timeout_remove(req->timeout);
+		req->timeout = NULL;
+	}
+
+	*req = new_req;
+	req->rssi = -128;
+
+	if (sec)
+		req->timeout = l_timeout_create(sec, scan_cancel, req, NULL);
 
-	if (scan_timeout)
-		l_timeout_remove(scan_timeout);
 
-	memset(scan_uuid, 0, sizeof(scan_uuid));
-	scan_rssi = -128;
-	scan_timeout = NULL;
-	net = node_get_net(node);
-	io = mesh_net_get_io(net);
-	scan_node = node;
-	mesh_io_register_recv_cb(io, prvb, sizeof(prvb),
-						prov_beacon_recv, node);
+	n = mesh_model_opcode_set(OP_REM_PROV_SCAN_START, scan_req);
+	scan_req[n++] = 5;
+	scan_req[n++] = sec;
+	if (flen) {
+		memcpy(scan_req + n, req->uuid, flen);
+		n += flen;
+	}
+
+	mesh_model_send(req->node, 0, req->server, APP_IDX_DEV_REMOTE,
+						req->net_idx, DEFAULT_TTL,
+						true, n, scan_req);
 
-	if (duration)
-		scan_timeout = l_timeout_create(duration, scan_cancel,
-								node, NULL);
+	initiator_scan_reg(manager_scan_result, req->node);
+
+	l_queue_push_tail(scans, req);
 
 	return l_dbus_message_new_method_return(msg);
 }
@@ -444,12 +752,7 @@ static struct l_dbus_message *cancel_scan_call(struct l_dbus *dbus,
 	if (strcmp(sender, node_get_owner(node)) || !node_is_provisioner(node))
 		return dbus_error(msg, MESH_ERROR_NOT_AUTHORIZED, NULL);
 
-	if (scan_node) {
-		if (scan_node != node)
-			return dbus_error(msg, MESH_ERROR_BUSY, NULL);
-
-		scan_cancel(NULL, node);
-	}
+	manager_scan_cancel(node);
 
 	return l_dbus_message_new_method_return(msg);
 }
@@ -814,6 +1117,8 @@ static void setup_management_interface(struct l_dbus_interface *iface)
 						"aya{sv}", "uuid", "options");
 	l_dbus_interface_method(iface, "ImportRemoteNode", 0, import_node_call,
 				"", "qyay", "primary", "count", "dev_key");
+	l_dbus_interface_method(iface, "Reprovision", 0, reprovision_call,
+					"", "qa{sv}", "unicast", "options");
 	l_dbus_interface_method(iface, "DeleteRemoteNode", 0, delete_node_call,
 						"", "qy", "primary", "count");
 	l_dbus_interface_method(iface, "UnprovisionedScan", 0, start_scan_call,
@@ -849,7 +1154,7 @@ bool manager_dbus_init(struct l_dbus *bus)
 	if (!l_dbus_register_interface(bus, MESH_MANAGEMENT_INTERFACE,
 						setup_management_interface,
 						NULL, false)) {
-		l_info("Unable to register %s interface",
+		l_debug("Unable to register %s interface",
 						MESH_MANAGEMENT_INTERFACE);
 		return false;
 	}
@@ -859,8 +1164,8 @@ bool manager_dbus_init(struct l_dbus *bus)
 
 void manager_scan_cancel(struct mesh_node *node)
 {
-	if (scan_node != node)
-		return;
+	struct scan_req *req;
 
-	scan_cancel(NULL, node);
+	while ((req = l_queue_find(scans, by_node, node)))
+		scan_cancel(NULL, req);
 }
diff --git a/mesh/mesh-config-json.c b/mesh/mesh-config-json.c
index 7f46c8582..8f321a731 100644
--- a/mesh/mesh-config-json.c
+++ b/mesh/mesh-config-json.c
@@ -58,6 +58,33 @@ static const char *cfgnode_name = "/node.json";
 static const char *bak_ext = ".bak";
 static const char *tmp_ext = ".tmp";
 
+/* JSON key words */
+static const char *unicastAddress = "unicastAddress";
+static const char *deviceCan = "deviceCan";
+static const char *deviceKey = "deviceKey";
+static const char *defaultTTL = "defaultTTL";
+static const char *sequenceNumber = "sequenceNumber";
+static const char *netKeys = "netKeys";
+static const char *appKeys = "appKeys";
+static const char *elements = "elements";
+static const char *models = "models";
+static const char *modelId = "modelId";
+static const char *address = "address";
+static const char *bind = "bind";
+static const char *publish = "publish";
+static const char *subscribe = "subscribe";
+static const char *boundNetKey = "boundNetKey";
+static const char *keyRefresh = "keyRefresh";
+static const char *subEnabled = "subEnabled";
+static const char *pubEnabled = "pubEnabled";
+static const char *retransmit = "retransmit";
+
+/* Common JSON values */
+static const char *enabled = "enabled";
+static const char *disabled = "disabled";
+static const char *unsupported = "unsupported";
+
+
 static bool save_config(json_object *jnode, const char *fname)
 {
 	FILE *outfile;
@@ -134,14 +161,14 @@ static int get_element_index(json_object *jnode, uint16_t ele_addr)
 	uint16_t addr, num_ele;
 	char *str;
 
-	if (!json_object_object_get_ex(jnode, "unicastAddress", &jvalue))
+	if (!json_object_object_get_ex(jnode, unicastAddress, &jvalue))
 		return -1;
 
 	str = (char *)json_object_get_string(jvalue);
 	if (sscanf(str, "%04hx", &addr) != 1)
 		return -1;
 
-	if (!json_object_object_get_ex(jnode, "elements", &jelements))
+	if (!json_object_object_get_ex(jnode, elements, &jelements))
 		return -1;
 
 	num_ele = json_object_array_length(jelements);
@@ -160,14 +187,14 @@ static json_object *get_element_model(json_object *jnode, int ele_idx,
 	size_t len;
 	char buf[9];
 
-	if (!json_object_object_get_ex(jnode, "elements", &jelements))
+	if (!json_object_object_get_ex(jnode, elements, &jelements))
 		return NULL;
 
 	jelement = json_object_array_get_idx(jelements, ele_idx);
 	if (!jelement)
 		return NULL;
 
-	if (!json_object_object_get_ex(jelement, "models", &jmodels))
+	if (!json_object_object_get_ex(jelement, models, &jmodels))
 		return NULL;
 
 	num_mods = json_object_array_length(jmodels);
@@ -189,7 +216,7 @@ static json_object *get_element_model(json_object *jnode, int ele_idx,
 		char *str;
 
 		jmodel = json_object_array_get_idx(jmodels, i);
-		if (!json_object_object_get_ex(jmodel, "modelId", &jvalue))
+		if (!json_object_object_get_ex(jmodel, modelId, &jvalue))
 			return NULL;
 
 		str = (char *)json_object_get_string(jvalue);
@@ -298,7 +325,7 @@ static bool read_unicast_address(json_object *jobj, uint16_t *unicast)
 	json_object *jvalue;
 	char *str;
 
-	if (!json_object_object_get_ex(jobj, "unicastAddress", &jvalue))
+	if (!json_object_object_get_ex(jobj, unicastAddress, &jvalue))
 		return false;
 
 	str = (char *)json_object_get_string(jvalue);
@@ -314,7 +341,7 @@ static bool read_default_ttl(json_object *jobj, uint8_t *ttl)
 	int val;
 
 	/* defaultTTL is optional */
-	if (!json_object_object_get_ex(jobj, "defaultTTL", &jvalue))
+	if (!json_object_object_get_ex(jobj, defaultTTL, &jvalue))
 		return true;
 
 	val = json_object_get_int(jvalue);
@@ -336,7 +363,7 @@ static bool read_seq_number(json_object *jobj, uint32_t *seq_number)
 	int val;
 
 	/* sequenceNumber is optional */
-	if (!json_object_object_get_ex(jobj, "sequenceNumber", &jvalue))
+	if (!json_object_object_get_ex(jobj, sequenceNumber, &jvalue))
 		return true;
 
 	val = json_object_get_int(jvalue);
@@ -396,7 +423,25 @@ static bool read_device_key(json_object *jobj, uint8_t key_buf[16])
 	if (!key_buf)
 		return false;
 
-	if (!json_object_object_get_ex(jobj, "deviceKey", &jvalue))
+	if (!json_object_object_get_ex(jobj, deviceKey, &jvalue))
+		return false;
+
+	str = (char *)json_object_get_string(jvalue);
+	if (!str2hex(str, strlen(str), key_buf, 16))
+		return false;
+
+	return true;
+}
+
+static bool read_candidate(json_object *jobj, uint8_t key_buf[16])
+{
+	json_object *jvalue;
+	char *str;
+
+	if (!key_buf)
+		return false;
+
+	if (!json_object_object_get_ex(jobj, deviceCan, &jvalue))
 		return false;
 
 	str = (char *)json_object_get_string(jvalue);
@@ -460,7 +505,7 @@ static bool read_app_keys(json_object *jobj, struct mesh_config_node *node)
 	int len;
 	int i;
 
-	if (!json_object_object_get_ex(jobj, "appKeys", &jarray))
+	if (!json_object_object_get_ex(jobj, appKeys, &jarray))
 		return true;
 
 	if (json_object_get_type(jarray) != json_type_array)
@@ -484,7 +529,7 @@ static bool read_app_keys(json_object *jobj, struct mesh_config_node *node)
 		if (!get_key_index(jtemp, "index", &appkey->app_idx))
 			goto fail;
 
-		if (!get_key_index(jtemp, "boundNetKey", &appkey->net_idx))
+		if (!get_key_index(jtemp, boundNetKey, &appkey->net_idx))
 			goto fail;
 
 		if (!json_object_object_get_ex(jtemp, "key", &jvalue))
@@ -516,7 +561,7 @@ static bool read_net_keys(json_object *jobj, struct mesh_config_node *node)
 	int i;
 
 	/* At least one NetKey must be present for a provisioned node */
-	if (!json_object_object_get_ex(jobj, "netKeys", &jarray))
+	if (!json_object_object_get_ex(jobj, netKeys, &jarray))
 		return false;
 
 	if (json_object_get_type(jarray) != json_type_array)
@@ -547,7 +592,7 @@ static bool read_net_keys(json_object *jobj, struct mesh_config_node *node)
 		if (!str2hex(str, strlen(str), netkey->new_key, 16))
 			goto fail;
 
-		if (!json_object_object_get_ex(jtemp, "keyRefresh", &jvalue))
+		if (!json_object_object_get_ex(jtemp, keyRefresh, &jvalue))
 			netkey->phase = KEY_REFRESH_PHASE_NONE;
 		else
 			netkey->phase = (uint8_t) json_object_get_int(jvalue);
@@ -598,7 +643,7 @@ bool mesh_config_net_key_add(struct mesh_config *cfg, uint16_t idx,
 	jnode = cfg->jnode;
 
 	l_debug("netKey %4.4x", idx);
-	json_object_object_get_ex(jnode, "netKeys", &jarray);
+	json_object_object_get_ex(jnode, netKeys, &jarray);
 	if (jarray)
 		jentry = get_key_object(jarray, idx);
 
@@ -616,14 +661,14 @@ bool mesh_config_net_key_add(struct mesh_config *cfg, uint16_t idx,
 	if (!add_key_value(jentry, "key", key))
 		goto fail;
 
-	json_object_object_add(jentry, "keyRefresh",
+	json_object_object_add(jentry, keyRefresh,
 				json_object_new_int(KEY_REFRESH_PHASE_NONE));
 
 	if (!jarray) {
 		jarray = json_object_new_array();
 		if (!jarray)
 			goto fail;
-		json_object_object_add(jnode, "netKeys", jarray);
+		json_object_object_add(jnode, netKeys, jarray);
 	}
 
 	json_object_array_add(jarray, jentry);
@@ -648,7 +693,7 @@ bool mesh_config_net_key_update(struct mesh_config *cfg, uint16_t idx,
 
 	jnode = cfg->jnode;
 
-	if (!json_object_object_get_ex(jnode, "netKeys", &jarray))
+	if (!json_object_object_get_ex(jnode, netKeys, &jarray))
 		return false;
 
 	jentry = get_key_object(jarray, idx);
@@ -667,7 +712,7 @@ bool mesh_config_net_key_update(struct mesh_config *cfg, uint16_t idx,
 	if (!add_key_value(jentry, "key", key))
 		return false;
 
-	json_object_object_add(jentry, "keyRefresh",
+	json_object_object_add(jentry, keyRefresh,
 				json_object_new_int(KEY_REFRESH_PHASE_ONE));
 
 	return save_config(jnode, cfg->node_dir_path);
@@ -682,20 +727,55 @@ bool mesh_config_net_key_del(struct mesh_config *cfg, uint16_t idx)
 
 	jnode = cfg->jnode;
 
-	if (!json_object_object_get_ex(jnode, "netKeys", &jarray))
+	if (!json_object_object_get_ex(jnode, netKeys, &jarray))
 		return true;
 
 	jarray_key_del(jarray, idx);
 
 	if (!json_object_array_length(jarray))
-		json_object_object_del(jnode, "netKeys");
+		json_object_object_del(jnode, netKeys);
 
 	return save_config(jnode, cfg->node_dir_path);
 }
 
 bool mesh_config_write_device_key(struct mesh_config *cfg, uint8_t *key)
 {
-	if (!cfg || !add_key_value(cfg->jnode, "deviceKey", key))
+	if (!cfg || !add_key_value(cfg->jnode, deviceKey, key))
+		return false;
+
+	return save_config(cfg->jnode, cfg->node_dir_path);
+}
+
+bool mesh_config_write_candidate(struct mesh_config *cfg, uint8_t *key)
+{
+	if (!cfg || !add_key_value(cfg->jnode, deviceCan, key))
+		return false;
+
+	return save_config(cfg->jnode, cfg->node_dir_path);
+}
+
+bool mesh_config_read_candidate(struct mesh_config *cfg, uint8_t *key)
+{
+	if (!cfg)
+		return false;
+
+	return read_candidate(cfg->jnode, key);
+}
+
+bool mesh_config_finalize_candidate(struct mesh_config *cfg)
+{
+	uint8_t key[16];
+
+	if (!cfg)
+		return false;
+
+	if (!read_candidate(cfg->jnode, key))
+		return false;
+
+	json_object_object_del(cfg->jnode, deviceCan);
+	json_object_object_del(cfg->jnode, deviceKey);
+
+	if (!add_key_value(cfg->jnode, deviceKey, key))
 		return false;
 
 	return save_config(cfg->jnode, cfg->node_dir_path);
@@ -719,7 +799,7 @@ bool mesh_config_app_key_add(struct mesh_config *cfg, uint16_t net_idx,
 
 	jnode = cfg->jnode;
 
-	json_object_object_get_ex(jnode, "appKeys", &jarray);
+	json_object_object_get_ex(jnode, appKeys, &jarray);
 	if (jarray)
 		jentry = get_key_object(jarray, app_idx);
 
@@ -734,7 +814,7 @@ bool mesh_config_app_key_add(struct mesh_config *cfg, uint16_t net_idx,
 	if (!write_int(jentry, "index", app_idx))
 		goto fail;
 
-	if (!write_int(jentry, "boundNetKey", net_idx))
+	if (!write_int(jentry, boundNetKey, net_idx))
 		goto fail;
 
 	if (!add_key_value(jentry, "key", key))
@@ -744,7 +824,7 @@ bool mesh_config_app_key_add(struct mesh_config *cfg, uint16_t net_idx,
 		jarray = json_object_new_array();
 		if (!jarray)
 			goto fail;
-		json_object_object_add(jnode, "appKeys", jarray);
+		json_object_object_add(jnode, appKeys, jarray);
 	}
 
 	json_object_array_add(jarray, jentry);
@@ -770,7 +850,7 @@ bool mesh_config_app_key_update(struct mesh_config *cfg, uint16_t app_idx,
 
 	jnode = cfg->jnode;
 
-	if (!json_object_object_get_ex(jnode, "appKeys", &jarray))
+	if (!json_object_object_get_ex(jnode, appKeys, &jarray))
 		return false;
 
 	/* The key entry should exist if the key is updated */
@@ -804,13 +884,13 @@ bool mesh_config_app_key_del(struct mesh_config *cfg, uint16_t net_idx,
 
 	jnode = cfg->jnode;
 
-	if (!json_object_object_get_ex(jnode, "appKeys", &jarray))
+	if (!json_object_object_get_ex(jnode, appKeys, &jarray))
 		return true;
 
 	jarray_key_del(jarray, idx);
 
 	if (!json_object_array_length(jarray))
-		json_object_object_del(jnode, "appKeys");
+		json_object_object_del(jnode, appKeys);
 
 	return save_config(jnode, cfg->node_dir_path);
 }
@@ -840,7 +920,7 @@ bool mesh_config_model_binding_add(struct mesh_config *cfg, uint16_t ele_addr,
 	if (!jmodel)
 		return false;
 
-	json_object_object_get_ex(jmodel, "bind", &jarray);
+	json_object_object_get_ex(jmodel, bind, &jarray);
 	if (jarray && jarray_has_string(jarray, buf, 4))
 		return true;
 
@@ -854,7 +934,7 @@ bool mesh_config_model_binding_add(struct mesh_config *cfg, uint16_t ele_addr,
 			json_object_put(jstring);
 			return false;
 		}
-		json_object_object_add(jmodel, "bind", jarray);
+		json_object_object_add(jmodel, bind, jarray);
 	}
 
 	json_object_array_add(jarray, jstring);
@@ -887,13 +967,13 @@ bool mesh_config_model_binding_del(struct mesh_config *cfg, uint16_t ele_addr,
 	if (!jmodel)
 		return false;
 
-	if (!json_object_object_get_ex(jmodel, "bind", &jarray))
+	if (!json_object_object_get_ex(jmodel, bind, &jarray))
 		return true;
 
 	jarray_string_del(jarray, buf, 4);
 
 	if (!json_object_array_length(jarray))
-		json_object_object_del(jmodel, "bind");
+		json_object_object_del(jmodel, bind);
 
 	return save_config(jnode, cfg->node_dir_path);
 }
@@ -963,7 +1043,7 @@ static struct mesh_config_pub *parse_model_publication(json_object *jpub)
 	int len, value;
 	char *str;
 
-	if (!json_object_object_get_ex(jpub, "address", &jvalue))
+	if (!json_object_object_get_ex(jpub, address, &jvalue))
 		return NULL;
 
 	str = (char *)json_object_get_string(jvalue);
@@ -998,9 +1078,10 @@ static struct mesh_config_pub *parse_model_publication(json_object *jpub)
 
 	if (!get_int(jpub, "credentials", &value))
 		goto fail;
+
 	pub->credential = (uint8_t) value;
 
-	if (!json_object_object_get_ex(jpub, "retransmit", &jvalue))
+	if (!json_object_object_get_ex(jpub, retransmit, &jvalue))
 		goto fail;
 
 	if (!get_int(jvalue, "count", &value))
@@ -1093,7 +1174,7 @@ static bool parse_models(json_object *jmodels, struct mesh_config_element *ele)
 
 		l_queue_push_tail(ele->models, mod);
 
-		if (!json_object_object_get_ex(jmodel, "modelId", &jvalue))
+		if (!json_object_object_get_ex(jmodel, modelId, &jvalue))
 			goto fail;
 
 		str = (char *)json_object_get_string(jvalue);
@@ -1112,29 +1193,32 @@ static bool parse_models(json_object *jmodels, struct mesh_config_element *ele)
 
 		mod->id = id;
 
-		if (json_object_object_get_ex(jmodel, "bind", &jarray)) {
+		if (len == 8)
+			mod->vendor = true;
+
+		if (json_object_object_get_ex(jmodel, bind, &jarray)) {
 			if (json_object_get_type(jarray) != json_type_array ||
 					!parse_bindings(jarray, mod))
 				goto fail;
 		}
 
-		if (json_object_object_get_ex(jmodel, "pubEnabled", &jvalue))
+		if (json_object_object_get_ex(jmodel, pubEnabled, &jvalue))
 			mod->pub_enabled = json_object_get_boolean(jvalue);
 		else
 			mod->pub_enabled = true;
 
-		if (json_object_object_get_ex(jmodel, "subEnabled", &jvalue))
+		if (json_object_object_get_ex(jmodel, subEnabled, &jvalue))
 			mod->sub_enabled = json_object_get_boolean(jvalue);
 		else
 			mod->sub_enabled = true;
 
-		if (json_object_object_get_ex(jmodel, "publish", &jvalue)) {
+		if (json_object_object_get_ex(jmodel, publish, &jvalue)) {
 			mod->pub = parse_model_publication(jvalue);
 			if (!mod->pub)
 				goto fail;
 		}
 
-		if (json_object_object_get_ex(jmodel, "subscribe", &jarray)) {
+		if (json_object_object_get_ex(jmodel, subscribe, &jarray)) {
 			if (!parse_model_subscriptions(jarray, mod))
 				goto fail;
 		}
@@ -1187,7 +1271,7 @@ static bool parse_elements(json_object *jelems, struct mesh_config_node *node)
 		if (sscanf(str, "%04hx", &(ele->location)) != 1)
 			goto fail;
 
-		if (json_object_object_get_ex(jelement, "models", &jmodels)) {
+		if (json_object_object_get_ex(jelement, models, &jmodels)) {
 			if (json_object_get_type(jmodels) != json_type_array ||
 						!parse_models(jmodels, ele))
 				goto fail;
@@ -1211,13 +1295,13 @@ static int get_mode(json_object *jvalue)
 	if (!str)
 		return 0xffffffff;
 
-	if (!strncasecmp(str, "disabled", strlen("disabled")))
+	if (!strncasecmp(str, disabled, strlen(disabled)))
 		return MESH_MODE_DISABLED;
 
-	if (!strncasecmp(str, "enabled", strlen("enabled")))
+	if (!strncasecmp(str, enabled, strlen(enabled)))
 		return MESH_MODE_ENABLED;
 
-	if (!strncasecmp(str, "unsupported", strlen("unsupported")))
+	if (!strncasecmp(str, unsupported, strlen(unsupported)))
 		return MESH_MODE_UNSUPPORTED;
 
 	return 0xffffffff;
@@ -1323,7 +1407,7 @@ static bool read_net_transmit(json_object *jobj, struct mesh_config_node *node)
 	uint16_t interval;
 	uint8_t cnt;
 
-	if (!json_object_object_get_ex(jobj, "retransmit", &jrtx))
+	if (!json_object_object_get_ex(jobj, retransmit, &jrtx))
 		return true;
 
 	if (!json_object_object_get_ex(jrtx, "count", &jvalue))
@@ -1386,7 +1470,7 @@ static bool read_node(json_object *jnode, struct mesh_config_node *node)
 	}
 
 	/* Check for required "elements" property */
-	if (!json_object_object_get_ex(jnode, "elements", &jvalue))
+	if (!json_object_object_get_ex(jnode, elements, &jvalue))
 		return false;
 
 	if (!read_net_transmit(jnode, node)) {
@@ -1460,11 +1544,11 @@ static const char *mode_to_string(int mode)
 {
 	switch (mode) {
 	case MESH_MODE_DISABLED:
-		return "disabled";
+		return disabled;
 	case MESH_MODE_ENABLED:
-		return "enabled";
+		return enabled;
 	default:
-		return "unsupported";
+		return unsupported;
 	}
 }
 
@@ -1522,7 +1606,7 @@ fail:
 
 bool mesh_config_write_unicast(struct mesh_config *cfg, uint16_t unicast)
 {
-	if (!cfg || !write_uint16_hex(cfg->jnode, "unicastAddress", unicast))
+	if (!cfg || !write_uint16_hex(cfg->jnode, unicastAddress, unicast))
 		return false;
 
 	return save_config(cfg->jnode, cfg->node_dir_path);
@@ -1558,8 +1642,8 @@ bool mesh_config_write_net_transmit(struct mesh_config *cfg, uint8_t cnt,
 	if (!write_int(jrtx, "interval", interval))
 		goto fail;
 
-	json_object_object_del(jnode, "retransmit");
-	json_object_object_add(jnode, "retransmit", jrtx);
+	json_object_object_del(jnode, retransmit);
+	json_object_object_add(jnode, retransmit, jrtx);
 
 	return save_config(cfg->jnode, cfg->node_dir_path);
 
@@ -1599,8 +1683,8 @@ static void add_model(void *a, void *b)
 	if (!jmodel)
 		return;
 
-	result = (mod->vendor) ? write_uint32_hex(jmodel, "modelId", mod->id) :
-			write_uint16_hex(jmodel, "modelId", (uint16_t) mod->id);
+	result = (mod->vendor) ? write_uint32_hex(jmodel, modelId, mod->id) :
+			write_uint16_hex(jmodel, modelId, (uint16_t) mod->id);
 
 	if (!result) {
 		json_object_put(jmodel);
@@ -1608,10 +1692,10 @@ static void add_model(void *a, void *b)
 	}
 
 	jval = json_object_new_boolean(mod->sub_enabled);
-	json_object_object_add(jmodel, "subEnabled", jval);
+	json_object_object_add(jmodel, subEnabled, jval);
 
 	jval = json_object_new_boolean(mod->pub_enabled);
-	json_object_object_add(jmodel, "pubEnabled", jval);
+	json_object_object_add(jmodel, pubEnabled, jval);
 
 	json_object_array_add(jmodels, jmodel);
 }
@@ -1663,11 +1747,11 @@ static struct mesh_config *create_config(const char *cfg_path,
 		return NULL;
 
 	/* Sequence number */
-	json_object_object_add(jnode, "sequenceNumber",
+	json_object_object_add(jnode, sequenceNumber,
 					json_object_new_int(node->seq_number));
 
 	/* Default TTL */
-	json_object_object_add(jnode, "defaultTTL",
+	json_object_object_add(jnode, defaultTTL,
 						json_object_new_int(node->ttl));
 
 	/* Elements */
@@ -1702,11 +1786,11 @@ static struct mesh_config *create_config(const char *cfg_path,
 		if (!jmodels)
 			goto fail;
 
-		json_object_object_add(jelement, "models", jmodels);
+		json_object_object_add(jelement, models, jmodels);
 		l_queue_foreach(ele->models, add_model, jmodels);
 	}
 
-	json_object_object_add(jnode, "elements", jelems);
+	json_object_object_add(jnode, elements, jelems);
 
 	cfg = l_new(struct mesh_config, 1);
 
@@ -1724,6 +1808,55 @@ fail:
 		return NULL;
 }
 
+void mesh_config_reset(struct mesh_config *cfg, struct mesh_config_node *node)
+{
+	json_object *jelems;
+	const struct l_queue_entry *entry;
+
+	if (!cfg || !cfg->jnode)
+		return;
+
+	/* TODO: Recreate Element Array */
+	jelems = json_object_new_array();
+	if (!jelems)
+		return;
+
+	entry = l_queue_get_entries(node->elements);
+
+	for (; entry; entry = entry->next) {
+		struct mesh_config_element *ele = entry->data;
+		json_object *jelement, *jmodels;
+
+		jelement = json_object_new_object();
+
+		if (!jelement) {
+			json_object_put(jelems);
+			return;
+		}
+
+		write_int(jelement, "elementIndex", ele->index);
+		write_uint16_hex(jelement, "location", ele->location);
+		json_object_array_add(jelems, jelement);
+
+		/* Models */
+		if (l_queue_isempty(ele->models))
+			continue;
+
+		jmodels = json_object_new_array();
+		if (!jmodels) {
+			json_object_put(jelems);
+			return;
+		}
+
+		json_object_object_add(jelement, models, jmodels);
+		l_queue_foreach(ele->models, add_model, jmodels);
+	}
+
+	/* Replace element array */
+	json_object_object_del(cfg->jnode, elements);
+	json_object_object_add(cfg->jnode, elements, jelems);
+}
+
 struct mesh_config *mesh_config_create(const char *cfgdir_name,
 		const uint8_t uuid[16], struct mesh_config_node *db_node)
 {
@@ -1768,7 +1901,7 @@ static void finish_key_refresh(json_object *jobj, uint16_t net_idx)
 	int i, len;
 
 	/* Clean up all the bound appkeys */
-	if (!json_object_object_get_ex(jobj, "appKeys", &jarray))
+	if (!json_object_object_get_ex(jobj, appKeys, &jarray))
 		return;
 
 	len = json_object_array_length(jarray);
@@ -1779,7 +1912,7 @@ static void finish_key_refresh(json_object *jobj, uint16_t net_idx)
 
 		jentry = json_object_array_get_idx(jarray, i);
 
-		if (!get_key_index(jentry, "boundNetKey", &idx))
+		if (!get_key_index(jentry, boundNetKey, &idx))
 			continue;
 
 		if (idx != net_idx)
@@ -1803,14 +1936,14 @@ bool mesh_config_net_key_set_phase(struct mesh_config *cfg, uint16_t idx,
 
 	jnode = cfg->jnode;
 
-	if (json_object_object_get_ex(jnode, "netKeys", &jarray))
+	if (json_object_object_get_ex(jnode, netKeys, &jarray))
 		jentry = get_key_object(jarray, idx);
 
 	if (!jentry)
 		return false;
 
-	json_object_object_del(jentry, "keyRefresh");
-	json_object_object_add(jentry, "keyRefresh",
+	json_object_object_del(jentry, keyRefresh);
+	json_object_object_add(jentry, keyRefresh,
 					json_object_new_int(phase));
 
 	if (phase == KEY_REFRESH_PHASE_NONE) {
@@ -1842,16 +1975,16 @@ bool mesh_config_model_pub_add(struct mesh_config *cfg, uint16_t ele_addr,
 	if (!jmodel)
 		return false;
 
-	json_object_object_del(jmodel, "publish");
+	json_object_object_del(jmodel, publish);
 
 	jpub = json_object_new_object();
 	if (!jpub)
 		return false;
 
 	if (pub->virt)
-		res = add_key_value(jpub, "address", pub->virt_addr);
+		res = add_key_value(jpub, address, pub->virt_addr);
 	else
-		res = write_uint16_hex(jpub, "address", pub->addr);
+		res = write_uint16_hex(jpub, address, pub->addr);
 
 	if (!res)
 		goto fail;
@@ -1878,8 +2011,8 @@ bool mesh_config_model_pub_add(struct mesh_config *cfg, uint16_t ele_addr,
 	if (!write_int(jrtx, "interval", pub->interval))
 		goto fail;
 
-	json_object_object_add(jpub, "retransmit", jrtx);
-	json_object_object_add(jmodel, "publish", jpub);
+	json_object_object_add(jpub, retransmit, jrtx);
+	json_object_object_add(jmodel, publish, jpub);
 
 	return save_config(jnode, cfg->node_dir_path);
 
@@ -1911,23 +2044,23 @@ bool mesh_config_model_pub_del(struct mesh_config *cfg, uint16_t addr,
 						uint32_t mod_id, bool vendor)
 {
 	if (!cfg || !delete_model_property(cfg->jnode, addr, mod_id, vendor,
-								"publish"))
+								publish))
 		return false;
 
 	return save_config(cfg->jnode, cfg->node_dir_path);
 }
 
-static void del_page(json_object *jarray, uint8_t page)
+static bool del_page(json_object *jarray, uint8_t page)
 {
 	char buf[3];
 	int i, len, ret;
 
 	if (!jarray)
-		return;
+		return false;
 
 	ret = snprintf(buf, 3, "%2.2x", page);
 	if (ret < 0)
-		return;
+		return false;
 
 	len = json_object_array_length(jarray);
 
@@ -1938,10 +2071,29 @@ static void del_page(json_object *jarray, uint8_t page)
 		jentry = json_object_array_get_idx(jarray, i);
 		str = (char *)json_object_get_string(jentry);
 
-		/* Delete matching page(s) */
-		if (!memcmp(str, buf, 2))
+		/* Delete matching page */
+		if (!memcmp(str, buf, 2)) {
 			json_object_array_del_idx(jarray, i, 1);
+			break;
+		}
 	}
+
+	return true;
+}
+
+void mesh_config_comp_page_del(struct mesh_config *cfg, uint8_t page)
+{
+	json_object *jnode, *jarray = NULL;
+
+	if (!cfg)
+		return;
+
+	jnode = cfg->jnode;
+
+	json_object_object_get_ex(jnode, "pages", &jarray);
+
+	if (del_page(jarray, page))
+		save_config(jnode, cfg->node_dir_path);
 }
 
 bool mesh_config_comp_page_add(struct mesh_config *cfg, uint8_t page,
@@ -1984,56 +2136,6 @@ bool mesh_config_comp_page_add(struct mesh_config *cfg, uint8_t page,
 	return save_config(jnode, cfg->node_dir_path);
 }
 
-bool mesh_config_comp_page_mv(struct mesh_config *cfg, uint8_t old, uint8_t nw)
-{
-	json_object *jnode, *jarray = NULL;
-	uint8_t *data;
-	char *str;
-	char old_buf[3];
-	int i, len, ret, dlen = 0;
-	bool status = true;
-
-	if (!cfg || old == nw)
-		return false;
-
-	ret = snprintf(old_buf, 3, "%2.2x", old);
-	if (ret < 0)
-		return false;
-
-	jnode = cfg->jnode;
-
-	json_object_object_get_ex(jnode, "pages", &jarray);
-
-	if (!jarray)
-		return false;
-
-	data = l_malloc(MAX_MSG_LEN);
-
-	len = json_object_array_length(jarray);
-
-	for (i = 0; i < len; i++) {
-		json_object *jentry;
-
-		jentry = json_object_array_get_idx(jarray, i);
-		str = (char *)json_object_get_string(jentry);
-
-		/* Delete matching page(s) but save data*/
-		if (!memcmp(str, old_buf, 2)) {
-			dlen = strlen(str + 2);
-			str2hex(str + 2, dlen, data, MAX_MSG_LEN);
-			dlen /= 2;
-			json_object_array_del_idx(jarray, i, 1);
-		}
-	}
-
-	if (dlen)
-		status = mesh_config_comp_page_add(cfg, nw, data, dlen);
-
-	l_free(data);
-
-	return status;
-}
-
 bool mesh_config_model_sub_add(struct mesh_config *cfg, uint16_t ele_addr,
 						uint32_t mod_id, bool vendor,
 						struct mesh_config_sub *sub)
@@ -2064,7 +2166,7 @@ bool mesh_config_model_sub_add(struct mesh_config *cfg, uint16_t ele_addr,
 		len = 32;
 	}
 
-	json_object_object_get_ex(jmodel, "subscribe", &jarray);
+	json_object_object_get_ex(jmodel, subscribe, &jarray);
 	if (jarray && jarray_has_string(jarray, buf, len))
 		return true;
 
@@ -2078,7 +2180,7 @@ bool mesh_config_model_sub_add(struct mesh_config *cfg, uint16_t ele_addr,
 			json_object_put(jstring);
 			return false;
 		}
-		json_object_object_add(jmodel, "subscribe", jarray);
+		json_object_object_add(jmodel, subscribe, jarray);
 	}
 
 	json_object_array_add(jarray, jstring);
@@ -2107,7 +2209,7 @@ bool mesh_config_model_sub_del(struct mesh_config *cfg, uint16_t ele_addr,
 	if (!jmodel)
 		return false;
 
-	if (!json_object_object_get_ex(jmodel, "subscribe", &jarray))
+	if (!json_object_object_get_ex(jmodel, subscribe, &jarray))
 		return true;
 
 	if (!sub->virt) {
@@ -2122,7 +2224,7 @@ bool mesh_config_model_sub_del(struct mesh_config *cfg, uint16_t ele_addr,
 	jarray_string_del(jarray, buf, len);
 
 	if (!json_object_array_length(jarray))
-		json_object_object_del(jmodel, "subscribe");
+		json_object_object_del(jmodel, subscribe);
 
 	return save_config(jnode, cfg->node_dir_path);
 }
@@ -2131,7 +2233,7 @@ bool mesh_config_model_sub_del_all(struct mesh_config *cfg, uint16_t addr,
 						uint32_t mod_id, bool vendor)
 {
 	if (!cfg || !delete_model_property(cfg->jnode, addr, mod_id, vendor,
-								"subscribe"))
+								subscribe))
 		return false;
 
 	return save_config(cfg->jnode, cfg->node_dir_path);
@@ -2161,7 +2263,7 @@ bool mesh_config_model_pub_enable(struct mesh_config *cfg, uint16_t ele_addr,
 	json_object_object_add(jmodel, "pubDisabled", jval);
 
 	if (!enable)
-		json_object_object_del(jmodel, "publish");
+		json_object_object_del(jmodel, publish);
 
 	return save_config(cfg->jnode, cfg->node_dir_path);
 }
@@ -2184,13 +2286,13 @@ bool mesh_config_model_sub_enable(struct mesh_config *cfg, uint16_t ele_addr,
 	if (!jmodel)
 		return false;
 
-	json_object_object_del(jmodel, "subEnabled");
+	json_object_object_del(jmodel, subEnabled);
 
 	jval = json_object_new_boolean(enable);
-	json_object_object_add(jmodel, "subEnabled", jval);
+	json_object_object_add(jmodel, subEnabled, jval);
 
 	if (!enable)
-		json_object_object_del(jmodel, "subscribe");
+		json_object_object_del(jmodel, subscribe);
 
 	return save_config(cfg->jnode, cfg->node_dir_path);
 }
@@ -2205,14 +2307,14 @@ bool mesh_config_write_seq_number(struct mesh_config *cfg, uint32_t seq,
 		return false;
 
 	if (!cache) {
-		if (!write_int(cfg->jnode, "sequenceNumber", seq))
+		if (!write_int(cfg->jnode, sequenceNumber, seq))
 			return false;
 
 		return mesh_config_save(cfg, true, NULL, NULL);
 	}
 
 	/* If resetting seq to Zero, make sure cached value reset as well */
-	if (seq && get_int(cfg->jnode, "sequenceNumber", &value))
+	if (seq && get_int(cfg->jnode, sequenceNumber, &value))
 		cached = (uint32_t)value;
 
 	/*
@@ -2262,8 +2364,8 @@ bool mesh_config_write_seq_number(struct mesh_config *cfg, uint32_t seq,
 
 		l_debug("Seq Cache: %d -> %d", seq, cached);
 
-		if (!write_int(cfg->jnode, "sequenceNumber", cached))
-		    return false;
+		if (!write_int(cfg->jnode, sequenceNumber, cached))
+			return false;
 
 		return mesh_config_save(cfg, false, NULL, NULL);
 	}
@@ -2273,7 +2375,7 @@ bool mesh_config_write_seq_number(struct mesh_config *cfg, uint32_t seq,
 
 bool mesh_config_write_ttl(struct mesh_config *cfg, uint8_t ttl)
 {
-	if (!cfg || !write_int(cfg->jnode, "defaultTTL", ttl))
+	if (!cfg || !write_int(cfg->jnode, defaultTTL, ttl))
 		return false;
 
 	return save_config(cfg->jnode, cfg->node_dir_path);
diff --git a/mesh/mesh-config.h b/mesh/mesh-config.h
index 420775829..ed1b610de 100644
--- a/mesh/mesh-config.h
+++ b/mesh/mesh-config.h
@@ -119,6 +119,7 @@ void mesh_config_release(struct mesh_config *cfg);
 void mesh_config_destroy_nvm(struct mesh_config *cfg);
 bool mesh_config_save(struct mesh_config *cfg, bool no_wait,
 				mesh_config_status_func_t cb, void *user_data);
+void mesh_config_reset(struct mesh_config *cfg, struct mesh_config_node *node);
 struct mesh_config *mesh_config_create(const char *cfgdir_name,
 						const uint8_t uuid[16],
 						struct mesh_config_node *node);
@@ -126,6 +127,9 @@ struct mesh_config *mesh_config_create(const char *cfgdir_name,
 bool mesh_config_write_net_transmit(struct mesh_config *cfg, uint8_t cnt,
 							uint16_t interval);
 bool mesh_config_write_device_key(struct mesh_config *cfg, uint8_t *key);
+bool mesh_config_write_candidate(struct mesh_config *cfg, uint8_t *key);
+bool mesh_config_read_candidate(struct mesh_config *cfg, uint8_t *key);
+bool mesh_config_finalize_candidate(struct mesh_config *cfg);
 bool mesh_config_write_token(struct mesh_config *cfg, uint8_t *token);
 bool mesh_config_write_network_key(struct mesh_config *cfg, uint16_t idx,
 				uint8_t *key, uint8_t *new_key, int phase);
@@ -141,7 +145,7 @@ bool mesh_config_write_mode(struct mesh_config *cfg, const char *keyword,
 								int value);
 bool mesh_config_comp_page_add(struct mesh_config *cfg, uint8_t page,
 						uint8_t *data, uint16_t size);
-bool mesh_config_comp_page_mv(struct mesh_config *cfg, uint8_t old, uint8_t nw);
+void mesh_config_comp_page_del(struct mesh_config *cfg, uint8_t page);
 bool mesh_config_model_binding_add(struct mesh_config *cfg, uint16_t ele_addr,
 						uint32_t mod_id, bool vendor,
 							uint16_t app_idx);
diff --git a/mesh/model.c b/mesh/model.c
index d48e6ef12..e2babea10 100644
--- a/mesh/model.c
+++ b/mesh/model.c
@@ -24,6 +24,8 @@
 #include "mesh/net.h"
 #include "mesh/appkey.h"
 #include "mesh/cfgmod.h"
+#include "mesh/prov.h"
+#include "mesh/remprv.h"
 #include "mesh/error.h"
 #include "mesh/dbus.h"
 #include "mesh/util.h"
@@ -76,6 +78,9 @@ static bool is_internal(uint32_t id)
 	if (id == CONFIG_SRV_MODEL || id == CONFIG_CLI_MODEL)
 		return true;
 
+	if (id == REM_PROV_SRV_MODEL || id == REM_PROV_CLI_MODEL)
+		return true;
+
 	return false;
 }
 
@@ -457,13 +462,25 @@ static int dev_packet_decrypt(struct mesh_node *node, const uint8_t *data,
 					dst, key_aid, seq, iv_idx, out, key))
 		return APP_IDX_DEV_LOCAL;
 
-	if (!keyring_get_remote_dev_key(node, src, dev_key))
+	key = dev_key;
+
+	if (keyring_get_remote_dev_key(node, src, dev_key)) {
+		if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict,
+				src, dst, key_aid, seq, iv_idx, out, key))
+			return APP_IDX_DEV_REMOTE;
+	}
+
+	/* See if there is a local Device Key Candidate as last resort */
+	if (!node_get_device_key_candidate(node, dev_key))
 		return -1;
 
-	key = dev_key;
-	if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict, src,
-					dst, key_aid, seq, iv_idx, out, key))
-		return APP_IDX_DEV_REMOTE;
+	if (mesh_crypto_payload_decrypt(NULL, 0, data, size, szmict,
+				src, dst, key_aid, seq, iv_idx, out, key)) {
+
+		/* If candidate dev_key worked, it is considered finalized */
+		node_finalize_candidate(node);
+		return APP_IDX_DEV_LOCAL;
+	}
 
 	return -1;
 }
diff --git a/mesh/node.c b/mesh/node.c
index cf4ed140e..5150a085a 100644
--- a/mesh/node.c
+++ b/mesh/node.c
@@ -27,9 +27,11 @@
 #include "mesh/appkey.h"
 #include "mesh/mesh-config.h"
 #include "mesh/provision.h"
+#include "mesh/prov.h"
 #include "mesh/keyring.h"
 #include "mesh/model.h"
 #include "mesh/cfgmod.h"
+#include "mesh/remprv.h"
 #include "mesh/util.h"
 #include "mesh/error.h"
 #include "mesh/dbus.h"
@@ -347,6 +349,15 @@ static bool add_elements_from_storage(struct mesh_node *node,
 		if (!add_element_from_storage(node, entry->data))
 			return false;
 
+	/* Add configuration server model on the primary element */
+	mesh_model_add(node, PRIMARY_ELE_IDX, CONFIG_SRV_MODEL, NULL);
+
+	/* Add remote provisioning models on the primary element */
+	mesh_model_add(node, PRIMARY_ELE_IDX, REM_PROV_SRV_MODEL, NULL);
+
+	if (node->provisioner)
+		mesh_model_add(node, PRIMARY_ELE_IDX, REM_PROV_CLI_MODEL, NULL);
+
 	return true;
 }
 
@@ -489,6 +500,10 @@ static bool init_from_storage(struct mesh_config_node *db_node,
 	/* Initialize configuration server model */
 	cfgmod_server_init(node, PRIMARY_ELE_IDX);
 
+	/* Initialize remote provisioning models */
+	remote_prov_server_init(node, PRIMARY_ELE_IDX);
+	remote_prov_client_init(node, PRIMARY_ELE_IDX);
+
 	node->cfg = cfg;
 
 	return true;
@@ -550,12 +565,78 @@ uint16_t node_get_primary(struct mesh_node *node)
 		return node->primary;
 }
 
+bool node_refresh(struct mesh_node *node, bool hard, void *prov_info)
+{
+	struct mesh_prov_node_info *info = prov_info;
+	bool res = true;
+
+	if (!node || !info)
+		return false;
+
+	if (!IS_UNICAST(info->unicast))
+		return false;
+
+	/* Changing Unicast addresses requires a hard node reset */
+	if (!hard && info->unicast != node->primary)
+		return false;
+
+	/*
+	 * Hard refresh results in immediate use of new Device Key.
+	 * Soft refresh saves new device key as Candidate until we
+	 * successfully receive new incoming message on that key.
+	 */
+	if (hard) {
+		if (!mesh_config_write_device_key(node->cfg, info->device_key))
+			return false;
+
+		memcpy(node->dev_key, info->device_key, sizeof(node->dev_key));
+
+	} else if (!mesh_config_write_candidate(node->cfg, info->device_key))
+		return false;
+
+	/* Replace Primary Unicast address if it has changed */
+	if (node->primary != info->unicast) {
+		res = mesh_config_write_unicast(node->cfg, info->unicast);
+		if (res) {
+			node->primary = info->unicast;
+			node->num_ele = info->num_ele;
+			mesh_net_register_unicast(node->net, node->primary,
+								node->num_ele);
+		}
+	}
+
+	/* Replace Page 0 with Page 128 if it exists */
+	if (res) {
+		if (node_replace_comp(node, 0, 128))
+			return true;
+	}
+
+	return res;
+}
+
 const uint8_t *node_get_device_key(struct mesh_node *node)
 {
 	if (!node)
 		return NULL;
-	else
-		return node->dev_key;
+
+	return node->dev_key;
+}
+
+bool node_get_device_key_candidate(struct mesh_node *node, uint8_t *key)
+{
+	if (!node)
+		return false;
+
+	return mesh_config_read_candidate(node->cfg, key);
+}
+
+void node_finalize_candidate(struct mesh_node *node)
+{
+	if (!node)
+		return;
+
+	if (mesh_config_read_candidate(node->cfg, node->dev_key))
+		mesh_config_finalize_candidate(node->cfg);
 }
 
 void node_set_token(struct mesh_node *node, uint8_t token[8])
@@ -785,7 +866,7 @@ uint8_t node_friend_mode_get(struct mesh_node *node)
 	return node->friend;
 }
 
-static uint16_t generate_node_comp(struct mesh_node *node, uint8_t *buf,
+static uint16_t node_generate_comp(struct mesh_node *node, uint8_t *buf,
 								uint16_t sz)
 {
 	uint16_t n, features, num_ele = 0;
@@ -895,6 +976,21 @@ static void convert_node_to_storage(struct mesh_node *node,
 
 }
 
+static void free_db_storage(struct mesh_config_node *db_node)
+{
+	const struct l_queue_entry *entry;
+
+	/* Free temporarily allocated resources */
+	entry = l_queue_get_entries(db_node->elements);
+	for (; entry; entry = entry->next) {
+		struct mesh_config_element *db_ele = entry->data;
+
+		l_queue_destroy(db_ele->models, l_free);
+	}
+
+	l_queue_destroy(db_node->elements, l_free);
+}
+
 static bool create_node_config(struct mesh_node *node, const uint8_t uuid[16])
 {
 	struct mesh_config_node db_node;
@@ -922,7 +1018,22 @@ static bool create_node_config(struct mesh_node *node, const uint8_t uuid[16])
 	return node->cfg != NULL;
 }
 
-static bool set_node_comp(struct mesh_node *node, uint8_t page_num,
+static void node_del_comp(struct mesh_node *node, uint8_t page_num)
+{
+	struct mesh_config_comp_page *page;
+
+	if (!node)
+		return;
+
+	page = l_queue_remove_if(node->pages, match_page,
+						L_UINT_TO_PTR(page_num));
+
+	l_free(page);
+
+	mesh_config_comp_page_del(node->cfg, page_num);
+}
+
+static bool node_set_comp(struct mesh_node *node, uint8_t page_num,
 					const uint8_t *data, uint16_t len)
 {
 	struct mesh_config_comp_page *page;
@@ -944,16 +1055,6 @@ static bool set_node_comp(struct mesh_node *node, uint8_t page_num,
 	return mesh_config_comp_page_add(node->cfg, page_num, page->data, len);
 }
 
-static bool create_node_comp(struct mesh_node *node)
-{
-	uint16_t len;
-	uint8_t comp[MAX_MSG_LEN - 2];
-
-	len = generate_node_comp(node, comp, sizeof(comp));
-
-	return set_node_comp(node, 0, comp, len);
-}
-
 const uint8_t *node_get_comp(struct mesh_node *node, uint8_t page_num,
 								uint16_t *len)
 {
@@ -975,6 +1076,7 @@ const uint8_t *node_get_comp(struct mesh_node *node, uint8_t page_num,
 bool node_replace_comp(struct mesh_node *node, uint8_t retire, uint8_t with)
 {
 	struct mesh_config_comp_page *old_page, *keep;
+	bool status;
 
 	if (!node)
 		return false;
@@ -989,9 +1091,13 @@ bool node_replace_comp(struct mesh_node *node, uint8_t retire, uint8_t with)
 
 	l_free(old_page);
 	keep->page_num = retire;
-	mesh_config_comp_page_mv(node->cfg, with, retire);
+	status = mesh_config_comp_page_add(node->cfg, keep->page_num,
+							keep->data, keep->len);
 
-	return true;
+	if (with != retire)
+		mesh_config_comp_page_del(node->cfg, with);
+
+	return status;
 }
 
 static void attach_io(void *a, void *b)
@@ -1170,8 +1276,13 @@ static bool get_element_properties(struct mesh_node *node, const char *path,
 	 * daemon. If the model is present in the application properties,
 	 * the operation below will be a "no-op".
 	 */
-	if (ele->idx == PRIMARY_ELE_IDX)
+	if (ele->idx == PRIMARY_ELE_IDX) {
 		mesh_model_add(node, ele->models, CONFIG_SRV_MODEL, NULL);
+		mesh_model_add(node, ele->models, REM_PROV_SRV_MODEL, NULL);
+		if (node->provisioner)
+			mesh_model_add(node, ele->models, REM_PROV_CLI_MODEL,
+									NULL);
+	}
 
 	return true;
 fail:
@@ -1232,6 +1343,15 @@ static bool get_app_properties(struct mesh_node *node, const char *path,
 	return true;
 }
 
+static void save_pages(void *data, void *user_data)
+{
+	struct mesh_config_comp_page *page = data;
+	struct mesh_node *node = user_data;
+
+	mesh_config_comp_page_add(node->cfg, page->page_num, page->data,
+								page->len);
+}
+
 static bool add_local_node(struct mesh_node *node, uint16_t unicast, bool kr,
 				bool ivu, uint32_t iv_idx, uint8_t dev_key[16],
 				uint16_t net_key_idx, uint8_t net_key[16])
@@ -1275,10 +1395,14 @@ static bool add_local_node(struct mesh_node *node, uint16_t unicast, bool kr,
 			return false;
 	}
 
+	l_queue_foreach(node->pages, save_pages, node);
+
 	update_net_settings(node);
 
-	/* Initialize configuration server model */
+	/* Initialize internal server models */
 	cfgmod_server_init(node, PRIMARY_ELE_IDX);
+	remote_prov_server_init(node, PRIMARY_ELE_IDX);
+	remote_prov_client_init(node, PRIMARY_ELE_IDX);
 
 	node->busy = true;
 
@@ -1326,39 +1450,59 @@ static void update_model_options(struct mesh_node *node,
 
 static bool check_req_node(struct managed_obj_request *req)
 {
+	struct mesh_node *node;
 	const int offset = 8;
 	uint16_t node_len, len;
 	uint8_t comp[MAX_MSG_LEN - 2];
 	const uint8_t *node_comp;
 
-	len = generate_node_comp(req->node, comp, sizeof(comp));
+	if (req->type != REQUEST_TYPE_ATTACH) {
+		node = req->node;
 
-	if (len < MIN_COMP_SIZE)
-		return false;
+		if (!create_node_config(node, node->uuid))
+			return false;
+	} else
+		node = req->attach;
 
-	node_comp = node_get_comp(req->attach, 0, &node_len);
+	node_comp = node_get_comp(node, 0, &node_len);
+	len = node_generate_comp(req->node, comp, sizeof(comp));
 
-	/* If no page 0 exists, create it and accept */
-	if (!node_len || !node_comp)
-		return set_node_comp(req->attach, 0, comp, len);
+	/* If no page 0 exists, then current composition as valid */
+	if (req->type != REQUEST_TYPE_ATTACH || !node_len)
+		goto page_zero_valid;
 
-	/* Test Element/Model part of composition and reject if changed */
+	/*
+	 * If composition has materially changed, save new composition
+	 * in page 128 until next NPPI procedure. But we do allow
+	 * for CID, PID, VID and/or CRPL to freely change without
+	 * requiring a NPPI procedure.
+	 */
 	if (node_len != len || memcmp(&node_comp[offset], &comp[offset],
 							node_len - offset))
-		return false;
+		return node_set_comp(node, 128, comp, len);
 
-	/* If comp has changed, but not Element/Models, resave and accept */
-	else if (memcmp(node_comp, comp, node_len))
-		return set_node_comp(req->attach, 0, comp, len);
+page_zero_valid:
+	/* If page 0 represents current App, ensure page 128 doesn't exist */
+	node_del_comp(node, 128);
 
-	/* Nothing has changed */
-	return true;
+	if (len == node_len && !memcmp(node_comp, comp, len))
+		return true;
+
+	return node_set_comp(node, 0, comp, len);
+}
+
+static bool is_zero(const void *a, const void *b)
+{
+	const struct node_element *element = a;
+
+	return !element->idx;
 }
 
 static bool attach_req_node(struct mesh_node *attach, struct mesh_node *node)
 {
 	const struct l_queue_entry *attach_entry;
 	const struct l_queue_entry *node_entry;
+	bool comp_changed = false;
 
 	attach->obj_path = node->obj_path;
 	node->obj_path = NULL;
@@ -1368,6 +1512,34 @@ static bool attach_req_node(struct mesh_node *attach, struct mesh_node *node)
 		return false;
 	}
 
+	if (attach->num_ele != node->num_ele) {
+		struct mesh_config_node db_node;
+		struct node_element *old_ele, *new_ele;
+
+		convert_node_to_storage(node, &db_node);
+
+		/*
+		 * If composition has materially changed, we need to discard
+		 * everything we knew about elements in the old application,
+		 * and start from what they are telling us now.
+		 */
+		old_ele = l_queue_remove_if(attach->elements, is_zero, NULL);
+		new_ele = l_queue_remove_if(node->elements, is_zero, NULL);
+		element_free(new_ele);
+
+		l_queue_destroy(attach->elements, element_free);
+		attach->elements = node->elements;
+		attach->num_ele = node->num_ele;
+
+		/* Restore primary elements */
+		l_queue_push_head(attach->elements, old_ele);
+
+		comp_changed = true;
+
+		mesh_config_reset(attach->cfg, &db_node);
+		free_db_storage(&db_node);
+	}
+
 	attach_entry = l_queue_get_entries(attach->elements);
 	node_entry = l_queue_get_entries(node->elements);
 
@@ -1384,6 +1556,10 @@ static bool attach_req_node(struct mesh_node *attach, struct mesh_node *node)
 
 		attach_entry = attach_entry->next;
 		node_entry = node_entry->next;
+
+		/* Only need the Primary element during Composition change */
+		if (comp_changed)
+			break;
 	}
 
 	mesh_agent_remove(attach->agent);
@@ -1399,8 +1575,12 @@ static bool attach_req_node(struct mesh_node *attach, struct mesh_node *node)
 	node->owner = NULL;
 
 	update_composition(node, attach);
+
 	update_model_options(node, attach);
 
+	if (comp_changed)
+		node->elements = NULL;
+
 	node_remove(node);
 
 	return true;
@@ -1499,16 +1679,7 @@ static void get_managed_objects_cb(struct l_dbus_message *msg, void *user_data)
 
 	node->num_ele = num_ele;
 
-	if (req->type != REQUEST_TYPE_ATTACH) {
-		/* Generate node configuration for a brand new node */
-		if (!create_node_config(node, node->uuid))
-			goto fail;
-
-		/* Create node composition */
-		if (!create_node_comp(node))
-			goto fail;
-	} else if (!check_req_node(req))
-		/* Check the integrity of the node composition */
+	if (!check_req_node(req))
 		goto fail;
 
 	switch (req->type) {
diff --git a/mesh/node.h b/mesh/node.h
index 2e3d89812..a98945223 100644
--- a/mesh/node.h
+++ b/mesh/node.h
@@ -38,6 +38,8 @@ uint16_t node_get_primary_net_idx(struct mesh_node *node);
 void node_set_token(struct mesh_node *node, uint8_t token[8]);
 const uint8_t *node_get_token(struct mesh_node *node);
 const uint8_t *node_get_device_key(struct mesh_node *node);
+bool node_get_device_key_candidate(struct mesh_node *node, uint8_t *key);
+void node_finalize_candidate(struct mesh_node *node);
 void node_set_num_elements(struct mesh_node *node, uint8_t num_ele);
 uint8_t node_get_num_elements(struct mesh_node *node);
 uint8_t node_default_ttl_get(struct mesh_node *node);
@@ -89,3 +91,4 @@ const char *node_get_storage_dir(struct mesh_node *node);
 bool node_load_from_storage(const char *storage_dir);
 void node_finalize_new_node(struct mesh_node *node, struct mesh_io *io);
 void node_property_changed(struct mesh_node *node, const char *property);
+bool node_refresh(struct mesh_node *node, bool hard, void *prov_info);
diff --git a/mesh/pb-adv.c b/mesh/pb-adv.c
index 180b16258..385d81d65 100644
--- a/mesh/pb-adv.c
+++ b/mesh/pb-adv.c
@@ -219,7 +219,7 @@ static void tx_timeout(struct l_timeout *timeout, void *user_data)
 	cb(user_data, 1);
 }
 
-static void pb_adv_tx(void *user_data, void *data, uint16_t len)
+static void pb_adv_tx(void *user_data, const void *data, uint16_t len)
 {
 	struct pb_adv_session *session = user_data;
 
@@ -478,7 +478,7 @@ static void pb_adv_packet(void *user_data, const uint8_t *pkt, uint16_t len)
 bool pb_adv_reg(bool initiator, mesh_prov_open_func_t open_cb,
 		mesh_prov_close_func_t close_cb,
 		mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb,
-		uint8_t uuid[16], void *user_data)
+		const uint8_t *uuid, void *user_data)
 {
 	struct pb_adv_session *session, *old_session;
 
diff --git a/mesh/pb-adv.h b/mesh/pb-adv.h
index 5b1e03dae..e33ba8e35 100644
--- a/mesh/pb-adv.h
+++ b/mesh/pb-adv.h
@@ -11,5 +11,5 @@
 bool pb_adv_reg(bool initiator, mesh_prov_open_func_t open_cb,
 		mesh_prov_close_func_t close_cb,
 		mesh_prov_receive_func_t rx_cb, mesh_prov_ack_func_t ack_cb,
-		uint8_t uuid[16], void *user_data);
+		const uint8_t *uuid, void *user_data);
 void pb_adv_unreg(void *user_data);
diff --git a/mesh/prov-acceptor.c b/mesh/prov-acceptor.c
index bf8c573da..fd9d4cd5d 100644
--- a/mesh/prov-acceptor.c
+++ b/mesh/prov-acceptor.c
@@ -22,6 +22,7 @@
 #include "mesh/net.h"
 #include "mesh/prov.h"
 #include "mesh/provision.h"
+#include "mesh/remprv.h"
 #include "mesh/pb-adv.h"
 #include "mesh/mesh.h"
 #include "mesh/agent.h"
@@ -169,9 +170,6 @@ static void acp_prov_open(void *user_data, prov_trans_tx_t trans_tx,
 					prov->transport != transport)
 		return;
 
-	if (transport != PB_ADV)
-		return;
-
 	prov->trans_tx = trans_tx;
 	prov->transport = transport;
 	prov->trans_data = trans_data;
@@ -425,9 +423,10 @@ static bool prov_start_check(struct prov_start *start,
 	return true;
 }
 
-static void acp_prov_rx(void *user_data, const uint8_t *data, uint16_t len)
+static void acp_prov_rx(void *user_data, const void *dptr, uint16_t len)
 {
 	struct mesh_prov_acceptor *rx_prov = user_data;
+	const uint8_t *data = dptr;
 	struct mesh_prov_node_info *info;
 	struct prov_fail_msg fail;
 	uint8_t type = *data++;
@@ -654,14 +653,19 @@ static void acp_prov_rx(void *user_data, const uint8_t *data, uint16_t len)
 		info->flags = prov->rand_auth_workspace[18];
 		info->iv_index = l_get_be32(prov->rand_auth_workspace + 19);
 		info->unicast = l_get_be16(prov->rand_auth_workspace + 23);
+		info->num_ele = prov->conf_inputs.caps.num_ele;
+
+		/* Send prov complete */
+		prov->rand_auth_workspace[0] = PROV_COMPLETE;
+		prov->trans_tx(prov->trans_data,
+				prov->rand_auth_workspace, 1);
 
 		result = prov->cmplt(prov->caller_data, PROV_ERR_SUCCESS, info);
 		prov->cmplt = NULL;
 		l_free(info);
 
 		if (result) {
-			prov->rand_auth_workspace[0] = PROV_COMPLETE;
-			prov_send(prov, prov->rand_auth_workspace, 1);
+			l_debug("PROV_COMPLETE");
 			goto cleanup;
 		} else {
 			fail.reason = PROV_ERR_UNEXPECTED_ERR;
@@ -721,7 +725,7 @@ static void acp_prov_ack(void *user_data, uint8_t msg_num)
 
 
 /* This starts unprovisioned device beacon */
-bool acceptor_start(uint8_t num_ele, uint8_t uuid[16],
+bool acceptor_start(uint8_t num_ele, uint8_t *uuid,
 		uint16_t algorithms, uint32_t timeout,
 		struct mesh_agent *agent,
 		mesh_prov_acceptor_complete_func_t complete_cb,
@@ -733,8 +737,10 @@ bool acceptor_start(uint8_t num_ele, uint8_t uuid[16],
 	uint8_t len = sizeof(beacon) - sizeof(uint32_t);
 	bool result;
 
-	/* Invoked from Join() method in mesh-api.txt, to join a
-	 * remote mesh network.
+	/*
+	 * Invoked from Join() method in mesh-api.txt, to join a
+	 * remote mesh network. May also be invoked with a NULL
+	 * uuid to perform a Device Key Refresh procedure.
 	 */
 
 	if (prov)
@@ -752,37 +758,50 @@ bool acceptor_start(uint8_t num_ele, uint8_t uuid[16],
 
 	caps = mesh_agent_get_caps(agent);
 
-	/* TODO: Should we sanity check values here or elsewhere? */
 	prov->conf_inputs.caps.num_ele = num_ele;
-	prov->conf_inputs.caps.pub_type = caps->pub_type;
-	prov->conf_inputs.caps.static_type = caps->static_type;
-	prov->conf_inputs.caps.output_size = caps->output_size;
-	prov->conf_inputs.caps.input_size = caps->input_size;
-
-	/* Store UINT16 values in Over-the-Air order, in packed structure
-	 * for crypto inputs
-	 */
 	l_put_be16(algorithms, &prov->conf_inputs.caps.algorithms);
-	l_put_be16(caps->output_action, &prov->conf_inputs.caps.output_action);
-	l_put_be16(caps->input_action, &prov->conf_inputs.caps.input_action);
-
-	/* Compose Unprovisioned Beacon */
-	memcpy(beacon + 2, uuid, 16);
-	l_put_be16(caps->oob_info, beacon + 18);
-	if (caps->oob_info & OOB_INFO_URI_HASH){
-		l_put_be32(caps->uri_hash, beacon + 20);
-		len += sizeof(uint32_t);
+
+	if (caps) {
+		/* TODO: Should we sanity check values here or elsewhere? */
+		prov->conf_inputs.caps.pub_type = caps->pub_type;
+		prov->conf_inputs.caps.static_type = caps->static_type;
+		prov->conf_inputs.caps.output_size = caps->output_size;
+		prov->conf_inputs.caps.input_size = caps->input_size;
+
+		/* Store UINT16 values in Over-the-Air order, in packed
+		 * structure for crypto inputs
+		 */
+		l_put_be16(caps->output_action,
+					&prov->conf_inputs.caps.output_action);
+		l_put_be16(caps->input_action,
+					&prov->conf_inputs.caps.input_action);
+
+		/* Populate Caps fields of beacon */
+		l_put_be16(caps->oob_info, beacon + 18);
+		if (caps->oob_info & OOB_INFO_URI_HASH) {
+			l_put_be32(caps->uri_hash, beacon + 20);
+			len += sizeof(uint32_t);
+		}
 	}
 
-	/* Infinitely Beacon until Canceled, or Provisioning Starts */
-	result = mesh_send_pkt(0, 500, beacon, len);
+	if (uuid) {
+		/* Compose Unprovisioned Beacon */
+		memcpy(beacon + 2, uuid, 16);
+
+		/* Infinitely Beacon until Canceled, or Provisioning Starts */
+		result = mesh_send_pkt(0, 500, beacon, len);
 
-	if (!result)
-		goto error_fail;
+		if (!result)
+			goto error_fail;
 
-	/* Always register for PB-ADV */
-	result = pb_adv_reg(false, acp_prov_open, acp_prov_close, acp_prov_rx,
-						acp_prov_ack, uuid, prov);
+		/* Always register for PB-ADV */
+		result = pb_adv_reg(false, acp_prov_open, acp_prov_close,
+					acp_prov_rx, acp_prov_ack, uuid, prov);
+	} else {
+		/* Run Device Key Refresh Procedure */
+		result = register_nppi_acceptor(acp_prov_open, acp_prov_close,
+					acp_prov_rx, acp_prov_ack, prov);
+	}
 
 	if (result)
 		return true;
diff --git a/mesh/prov-initiator.c b/mesh/prov-initiator.c
index c62577523..e549a00ca 100644
--- a/mesh/prov-initiator.c
+++ b/mesh/prov-initiator.c
@@ -21,10 +21,12 @@
 #include "mesh/crypto.h"
 #include "mesh/net.h"
 #include "mesh/node.h"
+#include "mesh/model.h"
 #include "mesh/keyring.h"
 #include "mesh/prov.h"
 #include "mesh/provision.h"
 #include "mesh/pb-adv.h"
+#include "mesh/remprv.h"
 #include "mesh/mesh.h"
 #include "mesh/agent.h"
 #include "mesh/error.h"
@@ -82,12 +84,16 @@ struct mesh_prov_initiator {
 	struct l_timeout *timeout;
 	uint32_t to_secs;
 	enum int_state	state;
-	enum trans_type transport;
 	uint16_t net_idx;
+	uint16_t svr_idx;
 	uint16_t unicast;
+	uint16_t server;
+	uint8_t transport;
 	uint8_t material;
 	uint8_t expected;
 	int8_t previous;
+	uint8_t out_num;
+	uint8_t rpr_state;
 	struct conf_input conf_inputs;
 	uint8_t calc_key[16];
 	uint8_t salt[16];
@@ -100,14 +106,22 @@ struct mesh_prov_initiator {
 	uint8_t uuid[16];
 };
 
+struct scan_req {
+	mesh_prov_initiator_scan_result_t scan_result;
+	struct mesh_node *node;
+	int count;
+};
+
 static struct mesh_prov_initiator *prov = NULL;
+static struct l_queue *scans;
 
 static void initiator_free(void)
 {
 	if (prov)
 		l_timeout_remove(prov->timeout);
 
-	mesh_send_cancel(&pkt_filter, sizeof(pkt_filter));
+	if (!prov->server)
+		mesh_send_cancel(&pkt_filter, sizeof(pkt_filter));
 
 	pb_adv_unreg(prov);
 
@@ -119,6 +133,15 @@ static void int_prov_close(void *user_data, uint8_t reason)
 {
 	struct mesh_prov_initiator *prov = user_data;
 	struct mesh_prov_node_info info;
+	uint8_t msg[4];
+	int n;
+
+	if (prov->server) {
+		n = mesh_model_opcode_set(OP_REM_PROV_LINK_CLOSE, msg);
+		msg[n++] = reason == PROV_ERR_SUCCESS ? 0x00 : 0x02;
+		mesh_model_send(prov->node, 0, prov->server, APP_IDX_DEV_REMOTE,
+				prov->svr_idx, DEFAULT_TTL, true, n, msg);
+	}
 
 	if (reason != PROV_ERR_SUCCESS) {
 		prov->complete_cb(prov->caller_data, reason, NULL);
@@ -626,9 +649,10 @@ static void int_prov_start_auth(const struct mesh_agent_prov_caps *prov_caps,
 	}
 }
 
-static void int_prov_rx(void *user_data, const uint8_t *data, uint16_t len)
+static void int_prov_rx(void *user_data, const void *dptr, uint16_t len)
 {
 	struct mesh_prov_initiator *rx_prov = user_data;
+	const uint8_t *data = dptr;
 	uint8_t *out;
 	uint8_t type = *data++;
 	uint8_t fail_code[2];
@@ -651,7 +675,7 @@ static void int_prov_rx(void *user_data, const uint8_t *data, uint16_t len)
 	if (type >= L_ARRAY_SIZE(expected_pdu_size) ||
 					len != expected_pdu_size[type]) {
 		l_error("Expected PDU size %d, Got %d (type: %2.2x)",
-			len, expected_pdu_size[type], type);
+			expected_pdu_size[type], len, type);
 		fail_code[1] = PROV_ERR_INVALID_FORMAT;
 		goto failure;
 	}
@@ -773,7 +797,12 @@ static void int_prov_rx(void *user_data, const uint8_t *data, uint16_t len)
 			goto failure;
 		}
 
-		if (!prov->data_req_cb(prov->caller_data,
+		if (prov->transport == PB_NPPI_00 ||
+						prov->transport == PB_NPPI_02) {
+			/* No App data needed */
+			initiator_prov_data(prov->svr_idx, prov->server,
+							prov->caller_data);
+		} else if (!prov->data_req_cb(prov->caller_data,
 					prov->conf_inputs.caps.num_ele)) {
 			l_error("Provisioning Failed-Data Get");
 			fail_code[1] = PROV_ERR_CANT_ASSIGN_ADDR;
@@ -851,6 +880,8 @@ static void int_prov_ack(void *user_data, uint8_t msg_num)
 
 static void initiator_open_cb(void *user_data, int err)
 {
+	uint8_t msg[20];
+	int n;
 	bool result;
 
 	if (!prov)
@@ -859,18 +890,30 @@ static void initiator_open_cb(void *user_data, int err)
 	if (err != MESH_ERROR_NONE)
 		goto fail;
 
-	/* Always register for PB-ADV */
-	result = pb_adv_reg(true, int_prov_open, int_prov_close, int_prov_rx,
-						int_prov_ack, prov->uuid, prov);
+	if (prov->server) {
+		n = mesh_model_opcode_set(OP_REM_PROV_LINK_OPEN, msg);
+
+		if (prov->transport <= PB_NPPI_02) {
+			msg[n++] = prov->transport;
+		} else {
+			memcpy(msg + n, prov->uuid, 16);
+			n += 16;
+		}
+
+		result = mesh_model_send(prov->node, 0, prov->server,
+					APP_IDX_DEV_REMOTE, prov->svr_idx,
+					DEFAULT_TTL, true, n, msg);
+	} else {
+		/* Always register for PB-ADV */
+		result = pb_adv_reg(true, int_prov_open, int_prov_close,
+				int_prov_rx, int_prov_ack, prov->uuid, prov);
+	}
 
 	if (!result) {
 		err = MESH_ERROR_FAILED;
 		goto fail;
 	}
 
-	if (!prov)
-		return;
-
 	prov->start_cb(prov->caller_data, MESH_ERROR_NONE);
 	return;
 fail:
@@ -878,10 +921,20 @@ fail:
 	initiator_free();
 }
 
-bool initiator_start(enum trans_type transport,
-		uint8_t uuid[16],
-		uint16_t max_ele,
-		uint32_t timeout, /* in seconds from mesh.conf */
+static void initiate_to(struct l_timeout *timeout, void *user_data)
+{
+	struct mesh_prov_initiator *rx_prov = user_data;
+
+	if (rx_prov != prov) {
+		l_timeout_remove(timeout);
+		return;
+	}
+
+	int_prov_close(user_data, PROV_ERR_TIMEOUT);
+}
+
+bool initiator_start(uint8_t transport, uint16_t server, uint16_t svr_idx,
+		uint8_t uuid[16], uint16_t max_ele, uint32_t timeout,
 		struct mesh_agent *agent,
 		mesh_prov_initiator_start_func_t start_cb,
 		mesh_prov_initiator_data_req_func_t data_req_cb,
@@ -904,6 +957,10 @@ bool initiator_start(enum trans_type transport,
 	prov->data_req_cb = data_req_cb;
 	prov->caller_data = caller_data;
 	prov->previous = -1;
+	prov->server = server;
+	prov->svr_idx = svr_idx;
+	prov->transport = transport;
+	prov->timeout = l_timeout_create(timeout, initiate_to, prov, NULL);
 	memcpy(prov->uuid, uuid, 16);
 
 	mesh_agent_refresh(prov->agent, initiator_open_cb, prov);
@@ -915,3 +972,182 @@ void initiator_cancel(void *user_data)
 {
 	initiator_free();
 }
+
+static void rpr_tx(void *user_data, const void *data, uint16_t len)
+{
+	struct mesh_prov_initiator *prov = user_data;
+	uint8_t msg[72];
+	int n;
+
+	n = mesh_model_opcode_set(OP_REM_PROV_PDU_SEND, msg);
+	msg[n++] = ++prov->out_num;
+	memcpy(msg + n, data, len);
+	l_debug("Send OB %2.2x, with packet type %d", msg[n], prov->out_num);
+	n += len;
+
+	prov->rpr_state = PB_REMOTE_STATE_OB_PKT_TX;
+	mesh_model_send(prov->node, 0, prov->server, APP_IDX_DEV_REMOTE,
+				prov->svr_idx, DEFAULT_TTL, true, n, msg);
+}
+
+static bool match_req_node(const void *a, const void *b)
+{
+	const struct scan_req *req = a;
+	const struct mesh_node *node = b;
+
+	return req->node == node;
+}
+
+static bool remprv_cli_pkt(uint16_t src, uint16_t unicast, uint16_t app_idx,
+					uint16_t net_idx, const uint8_t *data,
+					uint16_t size, const void *user_data)
+{
+	struct mesh_node *node = (struct mesh_node *) user_data;
+	const uint8_t *pkt = data;
+	struct scan_req *req;
+	uint32_t opcode;
+	uint16_t n;
+
+	if (mesh_model_opcode_get(pkt, size, &opcode, &n)) {
+		size -= n;
+		pkt += n;
+	} else
+		return false;
+
+	if (opcode < OP_REM_PROV_SCAN_CAP_GET ||
+					opcode > OP_REM_PROV_PDU_REPORT)
+		return false;
+
+	if (app_idx != APP_IDX_DEV_REMOTE && app_idx != APP_IDX_DEV_LOCAL)
+		return true;
+
+	/* Local Dev key only allowed for Loop-backs */
+	if (app_idx == APP_IDX_DEV_LOCAL && unicast != src)
+		return true;
+
+	if (prov && (prov->server != src || prov->node != node))
+		return true;
+
+	n = 0;
+
+	switch (opcode) {
+	default:
+		return false;
+
+	/* Provisioning Opcodes */
+	case OP_REM_PROV_LINK_STATUS:
+		if (size != 2 || !prov)
+			break;
+
+		if (pkt[0] == PB_REM_ERR_SUCCESS)
+			prov->rpr_state = pkt[1];
+
+		break;
+
+	case OP_REM_PROV_LINK_REPORT:
+		if (size != 2 || !prov)
+			return true;
+
+		if (pkt[0] != PB_REM_ERR_SUCCESS) {
+			if (pkt[0] == PB_REM_ERR_CLOSED_BY_DEVICE ||
+				pkt[0] == PB_REM_ERR_CLOSED_BY_SERVER)
+				int_prov_close(prov, pkt[1]);
+
+			break;
+		}
+
+
+		if (prov->rpr_state == PB_REMOTE_STATE_LINK_OPENING)
+			int_prov_open(prov, rpr_tx, prov, prov->transport);
+		else if (prov->rpr_state == PB_REMOTE_STATE_LINK_CLOSING) {
+			prov->rpr_state = PB_REMOTE_STATE_IDLE;
+			int_prov_close(prov, pkt[1]);
+			break;
+		}
+
+		prov->rpr_state = pkt[1];
+
+		break;
+
+	case OP_REM_PROV_PDU_REPORT:
+		int_prov_rx(prov, pkt + 1, size - 1);
+		break;
+
+	case OP_REM_PROV_PDU_OB_REPORT:
+		if (size != 1 || !prov)
+			break;
+
+		l_debug("Got Ack for OB %d", pkt[0]);
+		if (prov->rpr_state == PB_REMOTE_STATE_OB_PKT_TX &&
+							pkt[0] == prov->out_num)
+			int_prov_ack(prov, pkt[0]);
+
+		break;
+
+	/* Scan Opcodes */
+	case OP_REM_PROV_SCAN_CAP_STATUS:
+	case OP_REM_PROV_SCAN_STATUS:
+		break;
+
+	case OP_REM_PROV_SCAN_REPORT:
+	case OP_REM_PROV_EXT_SCAN_REPORT:
+		req = l_queue_find(scans, match_req_node, node);
+		if (req) {
+			req->scan_result(node, src,
+				opcode == OP_REM_PROV_EXT_SCAN_REPORT,
+				pkt, size);
+		}
+	}
+
+	return true;
+}
+
+void initiator_scan_reg(mesh_prov_initiator_scan_result_t scan_result,
+								void *user_data)
+{
+	struct scan_req *req;
+
+	if (!scans)
+		scans = l_queue_new();
+
+	req = l_queue_find(scans, match_req_node, user_data);
+	if (!req) {
+		req = l_new(struct scan_req, 1);
+		l_queue_push_head(scans, req);
+	}
+
+	req->scan_result = scan_result;
+	req->node = user_data;
+	req->count++;
+}
+
+void initiator_scan_unreg(void *user_data)
+{
+	struct scan_req *req;
+
+	req = l_queue_find(scans, match_req_node, user_data);
+	if (req) {
+		req->count--;
+		if (!req->count) {
+			l_queue_remove(scans, req);
+			l_free(req);
+		}
+	}
+}
+
+static void remprv_cli_unregister(void *user_data)
+{
+}
+
+static const struct mesh_model_ops ops = {
+	.unregister = remprv_cli_unregister,
+	.recv = remprv_cli_pkt,
+	.bind = NULL,
+	.sub = NULL,
+	.pub = NULL
+};
+
+void remote_prov_client_init(struct mesh_node *node, uint8_t ele_idx)
+{
+	mesh_model_register(node, ele_idx, REM_PROV_CLI_MODEL, &ops, node);
+}
diff --git a/mesh/prov.h b/mesh/prov.h
index 99e864c50..e86668fe4 100644
--- a/mesh/prov.h
+++ b/mesh/prov.h
@@ -39,14 +39,14 @@ enum mesh_prov_mode {
 
 struct mesh_prov;
 
-typedef void (*prov_trans_tx_t)(void *trans_data, void *data, uint16_t len);
+typedef void (*prov_trans_tx_t)(void *tx_data, const void *data, uint16_t len);
 typedef void (*mesh_prov_open_func_t)(void *user_data, prov_trans_tx_t trans_tx,
 					void *trans_data, uint8_t trans_type);
 
 typedef void (*mesh_prov_close_func_t)(void *user_data, uint8_t reason);
 typedef void (*mesh_prov_send_func_t)(bool success, struct mesh_prov *prov);
 typedef void (*mesh_prov_ack_func_t)(void *user_data, uint8_t msg_num);
-typedef void (*mesh_prov_receive_func_t)(void *user_data, const uint8_t *data,
+typedef void (*mesh_prov_receive_func_t)(void *user_data, const void *data,
 								uint16_t size);
 
 
diff --git a/mesh/provision.h b/mesh/provision.h
index 1634c4d40..cfeb6deba 100644
--- a/mesh/provision.h
+++ b/mesh/provision.h
@@ -70,10 +70,11 @@ struct mesh_agent;
 #define OOB_INFO_URI_HASH	0x0002
 
 /* PB_REMOTE not supported from unprovisioned state */
-enum trans_type {
-	PB_ADV = 0,
-	PB_GATT,
-};
+#define PB_NPPI_00	0x00
+#define PB_NPPI_01	0x01
+#define PB_NPPI_02	0x02
+#define PB_ADV		0x03 /* Internal only, and may be reassigned */
+#define PB_GATT		0x04 /* Internal only, and may be reassigned */
 
 #define PROV_FLAG_KR	0x01
 #define PROV_FLAG_IVU	0x02
@@ -101,15 +102,21 @@ typedef bool (*mesh_prov_initiator_complete_func_t)(void *user_data,
 					uint8_t status,
 					struct mesh_prov_node_info *info);
 
+typedef void (*mesh_prov_initiator_scan_result_t)(void *user_data,
+					uint16_t server, bool extended,
+					const uint8_t *data, uint16_t len);
+
 /* This starts unprovisioned device beacon */
-bool acceptor_start(uint8_t num_ele, uint8_t uuid[16],
+bool acceptor_start(uint8_t num_ele, uint8_t *uuid,
 			uint16_t algorithms, uint32_t timeout,
 			struct mesh_agent *agent,
 			mesh_prov_acceptor_complete_func_t complete_cb,
 			void *caller_data);
 void acceptor_cancel(void *user_data);
 
-bool initiator_start(enum trans_type transport,
+bool initiator_start(uint8_t transport,
+		uint16_t server,
+		uint16_t svr_idx,
 		uint8_t uuid[16],
 		uint16_t max_ele,
 		uint32_t timeout, /* in seconds from mesh.conf */
@@ -120,3 +127,7 @@ bool initiator_start(enum trans_type transport,
 		void *node, void *caller_data);
 void initiator_prov_data(uint16_t net_idx, uint16_t primary, void *caller_data);
 void initiator_cancel(void *caller_data);
+
+void initiator_scan_reg(mesh_prov_initiator_scan_result_t scan_result,
+							void *user_data);
+void initiator_scan_unreg(void *caller_data);
diff --git a/mesh/remprv-server.c b/mesh/remprv-server.c
new file mode 100644
index 000000000..cd583563e
--- /dev/null
+++ b/mesh/remprv-server.c
@@ -0,0 +1,908 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2020  Intel Corporation. All rights reserved.
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <sys/time.h>
+#include <ell/ell.h>
+
+#include "src/shared/ad.h"
+
+#include "mesh/mesh-defs.h"
+#include "mesh/mesh-io.h"
+#include "mesh/util.h"
+#include "mesh/node.h"
+#include "mesh/net.h"
+#include "mesh/appkey.h"
+#include "mesh/model.h"
+#include "mesh/prov.h"
+#include "mesh/provision.h"
+#include "mesh/pb-adv.h"
+#include "mesh/remprv.h"
+
+#define EXT_LIST_SIZE	60
+
+#define RPR_DEV_KEY	0x00
+#define RPR_ADDR	0x01
+#define RPR_COMP	0x02
+#define RPR_ADV		0xFF	/* Internal use only*/
+
+struct rem_scan_data {
+	struct mesh_node *node;
+	struct l_timeout *timeout;
+	uint8_t *list;
+	uint16_t client;
+	uint16_t oob_info;
+	uint16_t net_idx;
+	uint8_t state;
+	uint8_t scanned_limit;
+	uint8_t addr[6];
+	uint8_t uuid[16];
+	uint8_t to_secs;
+	uint8_t rxed_ads;
+	uint8_t ext_cnt;
+	bool fltr;
+	uint8_t ext[0];
+};
+
+static struct rem_scan_data *rpb_scan;
+
+struct rem_prov_data {
+	struct mesh_node *node;
+	struct l_timeout *timeout;
+	void *trans_data;
+	uint16_t client;
+	uint16_t net_idx;
+	uint8_t svr_pdu_num;
+	uint8_t cli_pdu_num;
+	uint8_t state;
+	uint8_t nppi_proc;
+	union {
+		struct {
+			mesh_prov_open_func_t open_cb;
+			mesh_prov_close_func_t close_cb;
+			mesh_prov_receive_func_t rx_cb;
+			mesh_prov_ack_func_t ack_cb;
+			struct mesh_prov_node_info info;
+		} nppi;
+		struct {
+			uint8_t uuid[17];
+			prov_trans_tx_t tx;
+		} adv;
+	} u;
+};
+
+static struct rem_prov_data *rpb_prov;
+
+static const uint8_t prvb[2] = {BT_AD_MESH_BEACON, 0x00};
+static const uint8_t pkt_filter = BT_AD_MESH_PROV;
+static const char *name = "Test Name";
+
+static const uint8_t zero[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+static void srv_open(void *user_data, prov_trans_tx_t adv_tx,
+					void *trans_data, uint8_t nppi_proc)
+{
+	struct rem_prov_data *prov = user_data;
+	uint8_t msg[5];
+	int n;
+
+	if (prov != rpb_prov || prov->state != PB_REMOTE_STATE_LINK_OPENING)
+		return;
+
+	l_debug("Remote Link open confirmed");
+	prov->u.adv.tx = adv_tx;
+	prov->trans_data = trans_data;
+	prov->state = PB_REMOTE_STATE_LINK_ACTIVE;
+
+	n = mesh_model_opcode_set(OP_REM_PROV_LINK_REPORT, msg);
+	msg[n++] = PB_REM_ERR_SUCCESS;
+	msg[n++] = prov->state;
+
+	mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL,
+				prov->net_idx, DEFAULT_TTL, true, n, msg);
+}
+
+static void srv_rx(void *user_data, const void *dptr, uint16_t len)
+{
+	struct rem_prov_data *prov = user_data;
+	const uint8_t *data = dptr;
+	uint8_t msg[69];
+	int n;
+
+	if (prov != rpb_prov || prov->state < PB_REMOTE_STATE_LINK_ACTIVE ||
+								len > 65)
+		return;
+
+	l_debug("Remote PB IB-PDU");
+
+	prov->svr_pdu_num++;
+	n = mesh_model_opcode_set(OP_REM_PROV_PDU_REPORT, msg);
+	msg[n++] = prov->svr_pdu_num;
+	memcpy(msg + n, data, len);
+	n += len;
+
+	mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL,
+				prov->net_idx, DEFAULT_TTL, true, n, msg);
+}
+
+static void srv_ack(void *user_data, uint8_t msg_num)
+{
+	struct rem_prov_data *prov = user_data;
+	uint8_t msg[4];
+	int n;
+
+	if (prov != rpb_prov || prov->state != PB_REMOTE_STATE_OB_PKT_TX)
+		return;
+
+	l_debug("Remote PB ACK");
+
+	prov->state = PB_REMOTE_STATE_LINK_ACTIVE;
+	n = mesh_model_opcode_set(OP_REM_PROV_PDU_OB_REPORT, msg);
+	msg[n++] = prov->cli_pdu_num;
+
+	mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL,
+				prov->net_idx, DEFAULT_TTL, true, n, msg);
+}
+
+static void srv_close(void *user_data, uint8_t reason)
+{
+	struct rem_prov_data *prov = user_data;
+	uint8_t msg[4];
+	int n;
+
+	if (prov != rpb_prov || prov->state < PB_REMOTE_STATE_LINK_ACTIVE)
+		return;
+
+	l_debug("Remote PB Close");
+
+	prov->state = PB_REMOTE_STATE_LINK_CLOSING;
+	n = mesh_model_opcode_set(OP_REM_PROV_LINK_REPORT, msg);
+	msg[n++] = prov->state;
+	msg[n++] = reason;
+
+	mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL,
+				prov->net_idx, DEFAULT_TTL, true, n, msg);
+}
+
+static void send_prov_status(struct rem_prov_data *prov, uint8_t status)
+{
+	uint16_t n;
+	uint8_t msg[5];
+	bool segmented = prov->state == PB_REMOTE_STATE_LINK_CLOSING ?
+								true : false;
+
+	n = mesh_model_opcode_set(OP_REM_PROV_LINK_STATUS, msg);
+	msg[n++] = status;
+	msg[n++] = prov->state;
+
+	l_info("RPB-Link Status(%d): dst %4.4x", prov->state, prov->client);
+
+	mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL,
+				prov->net_idx, DEFAULT_TTL, segmented, n, msg);
+}
+
+static void remprv_prov_cancel(struct l_timeout *timeout,
+						void *user_data)
+{
+	struct rem_prov_data *prov = user_data;
+
+	if (prov != rpb_prov)
+		return;
+
+	l_timeout_remove(prov->timeout);
+	l_free(prov);
+	rpb_prov = NULL;
+}
+
+static void deregister_ext_ad_type(uint8_t ad_type)
+{
+	uint8_t short_ad;
+
+	switch (ad_type) {
+	case BT_AD_MESH_BEACON:
+	case BT_AD_MESH_DATA:
+	case BT_AD_MESH_PROV:
+	case BT_AD_UUID16_SOME:
+	case BT_AD_UUID32_SOME:
+	case BT_AD_UUID128_SOME:
+	case BT_AD_NAME_SHORT:
+		return;
+
+	case BT_AD_UUID16_ALL:
+	case BT_AD_UUID32_ALL:
+	case BT_AD_UUID128_ALL:
+	case BT_AD_NAME_COMPLETE:
+		/* Automatically get short versions */
+		short_ad = ad_type - 1;
+		mesh_io_deregister_recv_cb(NULL, &short_ad, 1);
+
+		/* Fall Through */
+	default:
+		mesh_io_deregister_recv_cb(NULL, &ad_type, 1);
+		break;
+	}
+}
+
+static void remprv_scan_cancel(struct l_timeout *timeout,
+						void *user_data)
+{
+	struct rem_scan_data *scan = user_data;
+	uint8_t msg[22 + EXT_LIST_SIZE];
+	uint16_t i, n;
+
+	if (!scan || scan != rpb_scan)
+		return;
+
+	for (n = 0; n < scan->ext_cnt; n++)
+		deregister_ext_ad_type(scan->ext[n]);
+
+	if (scan->timeout == timeout) {
+		/* Return Extended Results */
+		if (scan->ext_cnt) {
+			/* Return Extended Result */
+			n = mesh_model_opcode_set(
+					OP_REM_PROV_EXT_SCAN_REPORT, msg);
+			msg[n++] = PB_REM_ERR_SUCCESS;
+			memcpy(msg + n, scan->uuid, 16);
+			n += 16;
+
+			if (scan->oob_info) {
+				l_put_le16(0, msg + n);
+				n += 2;
+			}
+
+			i = 0;
+			while (scan->list[i]) {
+				msg[n++] = scan->list[i];
+				memcpy(msg + n, &scan->list[i + 1],
+								scan->list[i]);
+				n += scan->list[i];
+				i += scan->list[i] + 1;
+			}
+		}
+	}
+
+	l_timeout_remove(scan->timeout);
+	l_free(scan->list);
+	l_free(scan);
+	rpb_scan = NULL;
+}
+
+static void scan_pkt(void *user_data, struct mesh_io_recv_info *info,
+					const uint8_t *data, uint16_t len)
+{
+	struct rem_scan_data *scan = user_data;
+	uint8_t msg[22 + EXT_LIST_SIZE];
+	uint16_t i, n;
+	uint8_t filled = 0;
+	bool report = false;
+
+	if (scan != rpb_scan)
+		return;
+
+	if (scan->ext_cnt)
+		goto extended_scan;
+
+	/* RX Unprovisioned Beacon */
+	if (data[0] != BT_AD_MESH_BEACON || data[1] ||
+			(len != 18 && len != 20 && len != 24))
+		return;
+
+	data += 2;
+	len -= 2;
+
+	for (n = 0; !report && n < scan->scanned_limit; n++) {
+		if (!memcmp(&scan->list[n * 17 + 1], data, 16)) {
+
+			/* Repeat UUID, check RSSI */
+			if ((int8_t) scan->list[n * 17] < info->rssi) {
+				report = true;
+				scan->list[n * 17] = (uint8_t) info->rssi;
+			}
+
+		} else if (!memcmp(&scan->list[n * 17 + 1], zero, 16)) {
+
+			/* Found Empty slot */
+			report = true;
+			scan->list[n * 17] = (uint8_t) info->rssi;
+			memcpy(&scan->list[n * 17 + 1], data, 16);
+		}
+
+		filled++;
+	}
+
+	if (!report)
+		return;
+
+	n = mesh_model_opcode_set(OP_REM_PROV_SCAN_REPORT, msg);
+	msg[n++] = (uint8_t) info->rssi;
+	memcpy(msg + n, data, len);
+	n += len;
+
+	/* Always return oob_info, even if it wasn't in beacon */
+	if (len == 16) {
+		l_put_le16(0, msg + n);
+		n += 2;
+	}
+
+	goto send_report;
+
+extended_scan:
+	if (data[0] == BT_AD_MESH_BEACON && !data[1]) {
+		if (len != 18 && len != 20 && len != 24)
+			return;
+
+		/* Check UUID */
+		if (memcmp(data + 2, scan->uuid, 16))
+			return;
+
+		/* Zero AD list if prior data RXed from different bd_addr */
+		if (memcmp(scan->addr, info->addr, 6)) {
+			scan->list[0] = 0;
+			scan->rxed_ads = 0;
+		}
+
+		memcpy(scan->addr, info->addr, 6);
+		scan->fltr = true;
+
+		if (len >= 20)
+			scan->oob_info = l_get_le16(data + 18);
+
+		if (scan->rxed_ads != scan->ext_cnt)
+			return;
+
+
+	} else if (data[0] != BT_AD_MESH_BEACON) {
+		if (!scan->fltr || !memcmp(scan->addr, info->addr, 6)) {
+			i = 0;
+			while (scan->list[i]) {
+				/* check if seen */
+				if (scan->list[i + 1] == data[0])
+					return;
+
+				i += scan->list[i] + 1;
+			}
+
+			/* Overflow Protection */
+			if (i + len + 1 > EXT_LIST_SIZE)
+				return;
+
+			scan->list[i] = len;
+			scan->list[i + len + 1] = 0;
+			memcpy(scan->list + i + 1, data, len);
+			scan->rxed_ads++;
+		}
+
+		if (scan->rxed_ads != scan->ext_cnt)
+			return;
+
+	} else
+		return;
+
+	n = mesh_model_opcode_set(OP_REM_PROV_EXT_SCAN_REPORT, msg);
+	msg[n++] = PB_REM_ERR_SUCCESS;
+	memcpy(msg + n, scan->uuid, 16);
+	n += 16;
+	l_put_le16(scan->oob_info, msg + n);
+	n += 2;
+
+	i = 0;
+	while (scan->list[i]) {
+		msg[n++] = scan->list[i];
+		memcpy(msg + n, &scan->list[i + 1], scan->list[i]);
+		n += scan->list[i];
+		i += scan->list[i];
+	}
+
+send_report:
+	print_packet("App Tx", msg, n);
+	mesh_model_send(scan->node, 0, scan->client, APP_IDX_DEV_LOCAL,
+				scan->net_idx, DEFAULT_TTL, true, n, msg);
+
+	/* Clean-up if we are done reporting*/
+	if (filled == scan->scanned_limit || scan->ext_cnt)
+		remprv_scan_cancel(NULL, scan);
+}
+
+static bool register_ext_ad_type(uint8_t ad_type, struct rem_scan_data *scan)
+{
+	uint8_t short_ad;
+
+	switch (ad_type) {
+	case BT_AD_MESH_PROV:
+	case BT_AD_UUID16_SOME:
+	case BT_AD_UUID32_SOME:
+	case BT_AD_UUID128_SOME:
+	case BT_AD_NAME_SHORT:
+		/* Illegal Requests */
+		return false;
+
+	case BT_AD_UUID16_ALL:
+	case BT_AD_UUID32_ALL:
+	case BT_AD_UUID128_ALL:
+	case BT_AD_NAME_COMPLETE:
+		/* Automatically get short versions */
+		short_ad = ad_type - 1;
+		mesh_io_register_recv_cb(NULL, &short_ad, 1, scan_pkt, scan);
+
+		/* Fall Through */
+	default:
+		mesh_io_register_recv_cb(NULL, &ad_type, 1, scan_pkt, scan);
+
+		/* Fall Through */
+
+	case BT_AD_MESH_BEACON:
+		/* Ignored/auto request */
+		break;
+	}
+
+	return true;
+}
+
+static void link_active(void *user_data)
+{
+	struct rem_prov_data *prov = user_data;
+	uint8_t msg[5];
+	int n;
+
+	if (prov != rpb_prov || prov->state != PB_REMOTE_STATE_LINK_OPENING)
+		return;
+
+	l_debug("Remote Link open confirmed");
+	prov->state = PB_REMOTE_STATE_LINK_ACTIVE;
+
+	n = mesh_model_opcode_set(OP_REM_PROV_LINK_REPORT, msg);
+	msg[n++] = PB_REM_ERR_SUCCESS;
+	msg[n++] = PB_REMOTE_STATE_LINK_ACTIVE;
+
+	mesh_model_send(prov->node, 0, prov->client, APP_IDX_DEV_LOCAL,
+				prov->net_idx, DEFAULT_TTL, true, n, msg);
+}
+
+bool register_nppi_acceptor(mesh_prov_open_func_t open_cb,
+					mesh_prov_close_func_t close_cb,
+					mesh_prov_receive_func_t rx_cb,
+					mesh_prov_ack_func_t ack_cb,
+					void *user_data)
+{
+	struct rem_prov_data *prov = rpb_prov;
+
+	if (!prov || prov->nppi_proc == RPR_ADV)
+		return false;
+
+	prov->u.nppi.open_cb = open_cb;
+	prov->u.nppi.close_cb = close_cb;
+	prov->u.nppi.rx_cb = rx_cb;
+	prov->u.nppi.ack_cb = ack_cb;
+	prov->trans_data = user_data;
+
+	open_cb(user_data, srv_rx, prov, prov->nppi_proc);
+
+	l_idle_oneshot(link_active, prov, NULL);
+
+	return true;
+}
+
+static bool nppi_cmplt(void *user_data, uint8_t status,
+					struct mesh_prov_node_info *info)
+{
+	struct rem_prov_data *prov = user_data;
+
+	if (prov != rpb_prov)
+		return false;
+
+	/* Save new info to apply on Link Close */
+	prov->u.nppi.info = *info;
+	return true;
+}
+
+static bool start_dev_key_refresh(struct mesh_node *node, uint8_t nppi_proc,
+						struct rem_prov_data *prov)
+{
+	uint8_t num_ele = node_get_num_elements(node);
+
+	prov->nppi_proc = nppi_proc;
+	return acceptor_start(num_ele, NULL, 0x0001, 60, NULL, nppi_cmplt,
+									prov);
+}
+
+static bool remprv_srv_pkt(uint16_t src, uint16_t unicast, uint16_t app_idx,
+					uint16_t net_idx, const uint8_t *data,
+					uint16_t size, const void *user_data)
+{
+	struct rem_prov_data *prov = rpb_prov;
+	struct rem_scan_data *scan = rpb_scan;
+	struct mesh_node *node = (struct mesh_node *) user_data;
+	const uint8_t *pkt = data;
+	bool segmented = false;
+	uint32_t opcode;
+	uint8_t msg[69];
+	uint8_t status;
+	uint16_t n;
+
+	if (app_idx != APP_IDX_DEV_LOCAL)
+		return false;
+
+	if (mesh_model_opcode_get(pkt, size, &opcode, &n)) {
+		size -= n;
+		pkt += n;
+	} else
+		return false;
+
+	n = 0;
+
+	switch (opcode) {
+	default:
+		return false;
+
+	case OP_REM_PROV_SCAN_CAP_GET:
+		if (size != 0)
+			return true;
+
+		/* Compose Scan Info Status */
+		n = mesh_model_opcode_set(OP_REM_PROV_SCAN_CAP_STATUS, msg);
+		msg[n++] = PB_REMOTE_MAX_SCAN_QUEUE_SIZE;
+		msg[n++] = 1; /* Active Scanning Supported */
+		break;
+
+	case OP_REM_PROV_EXT_SCAN_START:
+		if (!size || !pkt[0])
+			return true;
+
+		/* Size check the message */
+		if (pkt[0] + 18 == size) {
+			/* Range check the Timeout */
+			if (!pkt[size - 1] || pkt[size - 1] > 5)
+				return true;
+		} else if (pkt[0] + 1 != size)
+			return true;
+
+		/* Get local device extended info */
+		if (pkt[0] + 18 != size) {
+			n = mesh_model_opcode_set(
+					OP_REM_PROV_EXT_SCAN_REPORT, msg);
+			msg[n++] = PB_REM_ERR_SUCCESS;
+			memcpy(msg + n, node_uuid_get(node), 16);
+			n += 16;
+			l_put_le16(0, msg + n);
+			n += 2;
+			size--;
+			pkt++;
+
+			while (size--) {
+				if (*pkt++ == BT_AD_NAME_COMPLETE) {
+					msg[n] = strlen(name) + 1;
+					if (msg[n] > sizeof(msg) - n - 1)
+						msg[n] = sizeof(msg) - n - 1;
+					n++;
+					msg[n++] = BT_AD_NAME_COMPLETE;
+					memcpy(&msg[n], name, msg[n - 2] - 1);
+					n += msg[n - 2] - 1;
+					goto send_pkt;
+				}
+			}
+
+			/* Send internal report */
+			l_debug("Send internal extended info %d", n);
+			goto send_pkt;
+		}
+
+		status = PB_REM_ERR_SUCCESS;
+		if (scan) {
+			if (scan->client != src || scan->node != node ||
+						scan->ext_cnt != pkt[0])
+				status = PB_REM_ERR_SCANNING_CANNOT_START;
+			else if (memcmp(scan->ext, pkt + 1, pkt[0]))
+				status = PB_REM_ERR_SCANNING_CANNOT_START;
+			else if (memcmp(scan->uuid, pkt + 2, 16))
+				status = PB_REM_ERR_SCANNING_CANNOT_START;
+		}
+
+		if (status != PB_REM_ERR_SUCCESS) {
+			n = mesh_model_opcode_set(OP_REM_PROV_EXT_SCAN_REPORT,
+									msg);
+			msg[n++] = status;
+			memset(msg + n, 0, 16);
+			n += 16;
+			segmented = true;
+			break;
+		}
+
+		/* Ignore extended requests while already scanning */
+		if (scan)
+			return true;
+
+		scan = (void *) l_new(uint8_t,
+					sizeof(struct rem_scan_data) + pkt[0]);
+
+		/* Validate and register Extended AD types */
+		for (n = 0; n < pkt[0]; n++) {
+			if (!register_ext_ad_type(pkt[1 + n], scan)) {
+				/* Invalid AD type detected -- Undo */
+				while (n--)
+					deregister_ext_ad_type(pkt[1 + n]);
+
+				l_free(scan);
+				return true;
+			}
+		}
+
+		rpb_scan = scan;
+		scan->client = src;
+		scan->net_idx = net_idx;
+		memcpy(scan->uuid, pkt + size - 17, 16);
+		scan->ext_cnt = pkt[0];
+		memcpy(scan->ext, pkt + 1, pkt[0]);
+		scan->list = l_malloc(EXT_LIST_SIZE);
+		scan->list[0] = 0;
+
+		mesh_io_register_recv_cb(NULL, prvb, sizeof(prvb),
+								scan_pkt, scan);
+
+		scan->timeout = l_timeout_create(pkt[size-1],
+						remprv_scan_cancel, scan, NULL);
+		return true;
+
+	case OP_REM_PROV_SCAN_START:
+		if (size != 2 && size != 18)
+			return true;
+
+		/* Reject Timeout of Zero */
+		if (!pkt[1])
+			return true;
+
+		status = PB_REM_ERR_SUCCESS;
+		if (scan) {
+			if (scan->ext_cnt || scan->client != src ||
+							scan->node != node)
+				status = PB_REM_ERR_SCANNING_CANNOT_START;
+			else if (!!(scan->fltr) != !!(size != 18))
+				status = PB_REM_ERR_SCANNING_CANNOT_START;
+			else if (scan->fltr && memcmp(scan->uuid, pkt + 2, 16))
+				status = PB_REM_ERR_SCANNING_CANNOT_START;
+		}
+
+		if (status != PB_REM_ERR_SUCCESS) {
+			n = mesh_model_opcode_set(OP_REM_PROV_SCAN_STATUS, msg);
+			msg[n++] = status;
+			msg[n++] = scan ? scan->state : 0;
+			msg[n++] = scan ? scan->scanned_limit :
+						PB_REMOTE_MAX_SCAN_QUEUE_SIZE;
+			msg[n++] = scan ? scan->to_secs : 0;
+			break;
+		}
+
+		if (!scan)
+			scan = l_new(struct rem_scan_data, 1);
+
+		rpb_scan = scan;
+
+		if (size == 18) {
+			memcpy(scan->uuid, pkt + 2, 16);
+			scan->fltr = true;
+			scan->state = 0x02; /* Limited */
+		} else {
+			memset(scan->uuid, 0, 16);
+			scan->fltr = false;
+			scan->state = 0x01; /* Unlimited */
+		}
+
+		status = PB_REM_ERR_SUCCESS;
+		scan->client = src;
+		scan->net_idx = net_idx;
+		scan->node = node;
+
+		if (!scan->list)
+			scan->list = l_new(uint8_t,
+					23 * PB_REMOTE_MAX_SCAN_QUEUE_SIZE);
+
+		mesh_io_register_recv_cb(NULL, prvb, 2, scan_pkt, scan);
+
+		scan->to_secs = pkt[1];
+
+		if (pkt[0])
+			scan->scanned_limit = pkt[0];
+		else
+			scan->scanned_limit = PB_REMOTE_MAX_SCAN_QUEUE_SIZE;
+
+		scan->timeout = l_timeout_create(pkt[1],
+					remprv_scan_cancel, scan, NULL);
+
+		/* Fall Through */
+
+	case OP_REM_PROV_SCAN_GET:
+		/* Compose Scan Status */
+		n = mesh_model_opcode_set(OP_REM_PROV_SCAN_STATUS, msg);
+		msg[n++] = status;
+		msg[n++] = scan ? scan->state : 0;
+		msg[n++] = scan ? scan->scanned_limit :
+						PB_REMOTE_MAX_SCAN_QUEUE_SIZE;
+		msg[n++] = scan ? scan->to_secs : 0;
+		break;
+
+	case OP_REM_PROV_SCAN_STOP:
+		if (size != 0 || !scan)
+			return true;
+
+		remprv_scan_cancel(NULL, scan);
+		return true;
+
+	case OP_REM_PROV_LINK_GET:
+		if (size != 0 || !prov)
+			return true;
+
+		send_prov_status(prov, PB_REM_ERR_SUCCESS);
+		return true;
+
+	case OP_REM_PROV_LINK_OPEN:
+		/* Sanity check args */
+		if (size != 16 && size != 17 && size != 1)
+			return true;
+
+		if (size == 17 && (pkt[16] == 0 || pkt[16] > 0x3c))
+			return true;
+
+		if (size == 1 && pkt[0] > 0x02)
+			return true;
+
+		if (prov) {
+			if (prov->client != src || prov->node != node ||
+				(size == 1 && prov->nppi_proc != pkt[0]) ||
+				(size >= 16 && (prov->nppi_proc != RPR_ADV ||
+					memcmp(prov->u.adv.uuid, pkt, 16)))) {
+
+				/* Send Reject (in progress) */
+				send_prov_status(prov, PB_REM_ERR_CANNOT_OPEN);
+				n = mesh_model_opcode_set(
+						OP_REM_PROV_LINK_STATUS, msg);
+				msg[n++] = PB_REM_ERR_CANNOT_OPEN;
+				msg[n++] = PB_REMOTE_STATE_LINK_ACTIVE;
+				break;
+			}
+
+			/* Send redundant  Success */
+			send_prov_status(prov, PB_REM_ERR_SUCCESS);
+			return true;
+		}
+
+		if (scan && scan->client != src && scan->node != node) {
+			n = mesh_model_opcode_set(OP_REM_PROV_LINK_STATUS, msg);
+			msg[n++] = PB_REM_ERR_CANNOT_OPEN;
+			msg[n++] = PB_REMOTE_STATE_LINK_ACTIVE;
+			break;
+		}
+
+		print_packet("Remote Prov Link Open", pkt, size);
+
+		remprv_scan_cancel(NULL, scan);
+
+		rpb_prov = prov = l_new(struct rem_prov_data, 1);
+		prov->client = src;
+		prov->net_idx = net_idx;
+		prov->node = node;
+		prov->state = PB_REMOTE_STATE_LINK_OPENING;
+
+		if (size == 1) {
+			status = start_dev_key_refresh(node, pkt[0], prov);
+
+		} else {
+			if (size == 17)
+				prov->timeout = l_timeout_create(pkt[16],
+						remprv_prov_cancel, prov, NULL);
+
+
+			prov->nppi_proc = RPR_ADV;
+			memcpy(prov->u.adv.uuid, pkt, 16);
+			status = pb_adv_reg(true, srv_open, srv_close, srv_rx,
+							srv_ack, pkt, prov);
+		}
+
+		if (status)
+			send_prov_status(prov, PB_REM_ERR_SUCCESS);
+		else {
+			n = mesh_model_opcode_set(OP_REM_PROV_LINK_STATUS, msg);
+			msg[n++] = PB_REM_ERR_CANNOT_OPEN;
+			msg[n++] = PB_REMOTE_STATE_IDLE;
+			remprv_prov_cancel(NULL, prov);
+		}
+
+		return true;
+
+	case OP_REM_PROV_LINK_CLOSE:
+		if (size != 1)
+			return true;
+
+		if (!prov || prov->node != node || prov->client != src)
+			return true;
+
+		prov->state = PB_REMOTE_STATE_LINK_CLOSING;
+		mesh_io_send_cancel(NULL, &pkt_filter, sizeof(pkt_filter));
+		send_prov_status(prov, PB_REM_ERR_SUCCESS);
+		if (pkt[0] == 0x02) {
+			msg[0] = PROV_FAILED;
+			msg[1] = PROV_ERR_CANT_ASSIGN_ADDR;
+			if (prov->nppi_proc == RPR_ADV)
+				prov->u.adv.tx(prov->trans_data, msg, 2);
+			else
+				prov->u.nppi.rx_cb(prov->trans_data, msg, 2);
+		}
+
+		if (prov->nppi_proc == RPR_ADV)
+			pb_adv_unreg(prov);
+
+		else if (prov->nppi_proc <= RPR_COMP) {
+			/* Hard or Soft refresh of local node, based on NPPI */
+			node_refresh(prov->node, (prov->nppi_proc == RPR_ADDR),
+							&prov->u.nppi.info);
+		}
+
+		remprv_prov_cancel(NULL, prov);
+
+		return true;
+
+	case OP_REM_PROV_PDU_SEND:
+		if (!prov || prov->node != node || prov->client != src)
+			return true;
+
+		if (size < 2)
+			return true;
+
+
+		prov->cli_pdu_num = *pkt++;
+		size--;
+		prov->state = PB_REMOTE_STATE_OB_PKT_TX;
+
+		if (prov->nppi_proc == RPR_ADV)
+			prov->u.adv.tx(prov->trans_data, pkt, size);
+		else {
+			srv_ack(prov, prov->cli_pdu_num);
+			prov->u.nppi.rx_cb(prov->trans_data, pkt, size);
+		}
+
+		return true;
+	}
+
+send_pkt:
+	l_info("PB-SVR: src %4.4x dst %4.4x", unicast, src);
+	print_packet("App Tx", msg, n);
+	mesh_model_send(node, 0, src, APP_IDX_DEV_LOCAL,
+				net_idx, DEFAULT_TTL, segmented, n, msg);
+
+	return true;
+}
+
+static void remprv_srv_unregister(void *user_data)
+{
+}
+
+static const struct mesh_model_ops ops = {
+	.unregister = remprv_srv_unregister,
+	.recv = remprv_srv_pkt,
+	.bind = NULL,
+	.sub = NULL,
+	.pub = NULL
+};
+
+void remote_prov_server_init(struct mesh_node *node, uint8_t ele_idx)
+{
+	mesh_model_register(node, ele_idx, REM_PROV_SRV_MODEL, &ops, node);
+}
diff --git a/mesh/remprv.h b/mesh/remprv.h
new file mode 100644
index 000000000..49b4e2c7c
--- /dev/null
+++ b/mesh/remprv.h
@@ -0,0 +1,78 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2020  Intel Corporation. All rights reserved.
+ *
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ */
+
+#define REM_PROV_SRV_MODEL	SET_ID(SIG_VENDOR, 0x0004)
+#define REM_PROV_CLI_MODEL	SET_ID(SIG_VENDOR, 0x0005)
+
+#define PB_REMOTE_MAX_SCAN_QUEUE_SIZE	5
+
+#define PB_REMOTE_STATE_IDLE		0x00
+#define PB_REMOTE_STATE_LINK_OPENING	0x01
+#define PB_REMOTE_STATE_LINK_ACTIVE	0x02
+#define PB_REMOTE_STATE_OB_PKT_TX	0x03
+#define PB_REMOTE_STATE_LINK_CLOSING	0x04
+
+#define PB_REMOTE_TYPE_LOCAL	0x01
+#define PB_REMOTE_TYPE_ADV	0x02
+#define PB_REMOTE_TYPE_GATT	0x04
+
+#define PB_REMOTE_SCAN_TYPE_NONE	0x00
+#define PB_REMOTE_SCAN_TYPE_UNLIMITED	0x01
+#define PB_REMOTE_SCAN_TYPE_LIMITED	0x02
+#define PB_REMOTE_SCAN_TYPE_DETAILED	0x03
+
+/* Remote Provisioning Opcode List */
+#define OP_REM_PROV_SCAN_CAP_GET	0x804F
+#define OP_REM_PROV_SCAN_CAP_STATUS	0x8050
+#define OP_REM_PROV_SCAN_GET		0x8051
+#define OP_REM_PROV_SCAN_START		0x8052
+#define OP_REM_PROV_SCAN_STOP		0x8053
+#define OP_REM_PROV_SCAN_STATUS		0x8054
+#define OP_REM_PROV_SCAN_REPORT		0x8055
+#define OP_REM_PROV_EXT_SCAN_START	0x8056
+#define OP_REM_PROV_EXT_SCAN_REPORT	0x8057
+#define OP_REM_PROV_LINK_GET		0x8058
+#define OP_REM_PROV_LINK_OPEN		0x8059
+#define OP_REM_PROV_LINK_CLOSE		0x805A
+#define OP_REM_PROV_LINK_STATUS		0x805B
+#define OP_REM_PROV_LINK_REPORT		0x805C
+#define OP_REM_PROV_PDU_SEND		0x805D
+#define OP_REM_PROV_PDU_OB_REPORT	0x805E
+#define OP_REM_PROV_PDU_REPORT		0x805F
+
+/* Remote Provisioning Errors */
+#define PB_REM_ERR_SUCCESS			0x00
+#define PB_REM_ERR_SCANNING_CANNOT_START	0x01
+#define PB_REM_ERR_INVALID_STATE		0x02
+#define PB_REM_ERR_LIMITED_RESOURCES		0x03
+#define PB_REM_ERR_CANNOT_OPEN			0x04
+#define PB_REM_ERR_OPEN_FAILED			0x05
+#define PB_REM_ERR_CLOSED_BY_DEVICE		0x06
+#define PB_REM_ERR_CLOSED_BY_SERVER		0x07
+#define PB_REM_ERR_CLOSED_BY_CLIENT		0x08
+#define PB_REM_ERR_CLOSED_CANNOT_RX_PDU		0x09
+#define PB_REM_ERR_CLOSED_CANNOT_TX_PDU		0x0A
+
+void remote_prov_server_init(struct mesh_node *node, uint8_t ele_idx);
+void remote_prov_client_init(struct mesh_node *node, uint8_t ele_idx);
+bool register_nppi_acceptor(mesh_prov_open_func_t open_cb,
+					mesh_prov_close_func_t close_cb,
+					mesh_prov_receive_func_t rx_cb,
+					mesh_prov_ack_func_t ack_cb,
+					void *user_data);
-- 
2.39.0


  parent reply	other threads:[~2023-01-19 23:58 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-01-19 23:57 [PATCH BlueZ 00/12] Mesh v1.1 additions Brian Gix
2023-01-19 23:57 ` [PATCH BlueZ 01/12] doc/mesh: Add Remote Provisioning DBus APIs Brian Gix
2023-01-20  4:12   ` Mesh v1.1 additions bluez.test.bot
2023-01-19 23:57 ` Brian Gix [this message]
2023-01-19 23:57 ` [PATCH BlueZ 03/12] tools/mesh: Optimize for multiple RPR servers and NPPI Brian Gix
2023-01-19 23:57 ` [PATCH BlueZ 04/12] mesh: Rename parameter list per crypto usage Brian Gix
2023-01-19 23:57 ` [PATCH BlueZ 05/12] unit/mesh: Add unit testing of Mesh Private Beaconing Brian Gix
2023-01-19 23:57 ` [PATCH BlueZ 06/12] mesh: Add storage of Mesh Private Beacon settings Brian Gix
2023-01-19 23:57 ` [PATCH BlueZ 07/12] mesh: Add Mesh Private Beacon server Brian Gix
2023-01-19 23:57 ` [PATCH BlueZ 08/12] mesh: Add Tx/Rx support of Mesh Private Beacons Brian Gix
2023-01-19 23:57 ` [PATCH BlueZ 09/12] mesh: Add internal Mesh Private Beacon model Brian Gix
2023-01-19 23:57 ` [PATCH BlueZ 10/12] mesh: Plumb in " Brian Gix
2023-01-19 23:57 ` [PATCH BlueZ 11/12] tools/mesh: Add support for Mesh Private Beacons Brian Gix
2023-01-19 23:57 ` [PATCH BlueZ 12/12] mesh: Switch beaconing net key Brian Gix

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20230119235755.46002-3-brian.gix@gmail.com \
    --to=brian.gix@gmail.com \
    --cc=brian.gix@intel.com \
    --cc=linux-bluetooth@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.