/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2023  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];
	uint8_t addr[6];
	uint16_t i, n;
	int8_t rssi;
	uint8_t filled = 0;
	bool report = false;

	if (scan != rpb_scan)
		return;

	if (info) {
		rssi = info->rssi;
		memcpy(addr, info->addr, 6);
	} else {
		rssi = 0;
		memset(addr, 0, 6);
	}

	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] < rssi) {
				report = true;
				scan->list[n * 17] = (uint8_t) rssi;
			}

		} else if (!memcmp(&scan->list[n * 17 + 1], zero, 16)) {

			/* Found Empty slot */
			report = true;
			scan->list[n * 17] = (uint8_t) 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) 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, addr, 6)) {
			scan->list[0] = 0;
			scan->rxed_ads = 0;
		}

		memcpy(scan->addr, 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, 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 old_state, 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 */
		}

		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++] = PB_REM_ERR_SUCCESS;
		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;

		old_state = prov->state;
		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 &&
				old_state >= PB_REMOTE_STATE_LINK_ACTIVE) {
			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);
}
