/*
 * Copyright (c) 2017 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "mesh_cfg.h"
#include "mesh_kernel.h"
#include "mesh_errno.h"
#include "mesh_util.h"
#include "sys/byteorder.h"
#include "net/buf.h"
#include "bluetooth/mesh_bluetooth.h"
#include "api/mesh.h"

#include "bluetooth/bt_str.h"

#include "testing.h"

#include "mesh.h"
#include "net.h"
#include "prov.h"
#include "crypto.h"
#include "beacon.h"
#include "cfg.h"

#define LOG_LEVEL CONFIG_BT_MESH_BEACON_LOG_LEVEL
#include "api/mesh_log.h"

#define PROVISIONED_INTERVAL       K_SECONDS(10)

#define BEACON_TYPE_UNPROVISIONED  0x00
#define BEACON_TYPE_SECURE         0x01
#define BEACON_TYPE_PRIVATE        0x02

/* 3 transmissions, 20ms interval */
#define UNPROV_XMIT                BT_MESH_TRANSMIT(2, 20)

/* 1 transmission, 20ms interval */
#if (CONFIG_BT_TESTING)
// Work around for private beacon test
#define PROV_XMIT                  BT_MESH_TRANSMIT(10, 20)
#else
#define PROV_XMIT                  BT_MESH_TRANSMIT(0, 20)
#endif

static struct k_work_delayable beacon_timer;
static struct bt_mesh_subnet *beacon_send_sub_curr;

#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
static struct {
	/**
	 * Identifier for the current Private beacon random-value.
	 * Each time we regenerate the random-value, we'll update this idx.
	 * Whenever it's time for a subnet to create a beacon, it'll compare
	 * the subnet's beacon idx to determine whether the random value has
	 * changed since the last beacon was sent. If this is the case, we'll
	 * regenerate the beacon based on the new random value.
	 */
	uint16_t idx;
	uint8_t val[13];
	uint64_t timestamp;
} priv_random;
#endif

struct beacon_params {
	bool private;
	union {
		const uint8_t *net_id;
		struct {
			const uint8_t *data;
			const uint8_t *random;
		};
	};
	const uint8_t *auth;
	uint32_t iv_index;
	uint8_t flags;

	bool new_key;
};

#if (CONFIG_BT_MESH_PRIV_BEACONS)
static int private_beacon_create(struct bt_mesh_subnet *sub,
				 struct net_buf_simple *buf);
static int private_beacon_update(struct bt_mesh_subnet *sub);
#endif

#if defined(CONFIG_BT_TESTING)
#if (CONFIG_MESH_CB_REGISTERED)
static struct bt_mesh_beacon_cb *p_beacon_list = NULL;
void bt_mesh_beacon_cb_register(struct bt_mesh_beacon_cb *p_beacon_cb)
{
	struct bt_mesh_beacon_cb  *p_cur_beacon_cb = NULL;
	if (p_beacon_cb) {
		if (p_beacon_list == NULL) {
			p_beacon_list = p_beacon_cb;
			p_beacon_cb->next = NULL;
		}
		else {
			p_cur_beacon_cb = p_beacon_list;
			while(p_cur_beacon_cb->next != NULL) {
				p_cur_beacon_cb = p_cur_beacon_cb->next;
			}
			p_cur_beacon_cb->next = p_beacon_cb;
			p_beacon_cb->next = NULL;
		}
	}
}
#else
extern uint32_t _beacon_cbs[];
extern uint32_t _ebeacon_cbs[];
#endif
#endif

static struct bt_mesh_beacon *subnet_beacon_get_by_type(struct bt_mesh_subnet *sub, bool priv)
{
#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
	return priv ? &sub->priv_beacon : &sub->secure_beacon;
#else
	return &sub->secure_beacon;
#endif
}

static bool beacon_cache_match(struct bt_mesh_subnet *sub, void *data)
{
	struct beacon_params *params;
	struct bt_mesh_beacon *beacon;

	params = data;
	beacon = subnet_beacon_get_by_type(sub, params->private);

	return !memcmp(beacon->cache, params->auth, sizeof(beacon->cache));
}

static void cache_add(const uint8_t auth[8], struct bt_mesh_beacon *beacon)
{
	memcpy(beacon->cache, auth, sizeof(beacon->cache));
}

void bt_mesh_beacon_cache_clear(struct bt_mesh_subnet *sub)
{
	(void)memset(sub->secure_beacon.cache, 0, sizeof(sub->secure_beacon.cache));
#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
	(void)memset(sub->priv_beacon.cache, 0, sizeof(sub->priv_beacon.cache));
#endif
}

static void beacon_start(uint16_t duration, int err, void *user_data)
{
	if (err) {
		LOG_ERR("Failed to send beacon: err %d", err);
		if (beacon_send_sub_curr) {
			k_work_reschedule(&beacon_timer, K_NO_WAIT);
		}
	}
}

static void beacon_complete(int err, void *user_data)
{
	struct bt_mesh_beacon *beacon = user_data;

	LOG_DBG("err %d", err);
	beacon->sent = k_uptime_get_32();

	if (beacon_send_sub_curr) {
		k_work_reschedule(&beacon_timer, K_MSEC(20));
	}
}

static int secure_beacon_create(struct bt_mesh_subnet *sub,
				struct net_buf_simple *buf)
{
	uint8_t flags = bt_mesh_net_flags(sub);
	struct bt_mesh_subnet_keys *keys;

	net_buf_simple_add_u8(buf, BEACON_TYPE_SECURE);

	keys = &sub->keys[SUBNET_KEY_TX_IDX(sub)];

	net_buf_simple_add_u8(buf, flags);

	/* Network ID */
	net_buf_simple_add_mem(buf, keys->net_id, 8);

	/* IV Index */
	net_buf_simple_add_be32(buf, bt_mesh.iv_index);

	net_buf_simple_add_mem(buf, sub->secure_beacon.auth, 8);

	LOG_DBG("net_idx 0x%04x flags 0x%02x NetID %s", sub->net_idx, flags,
		bt_hex(keys->net_id, 8));
	LOG_DBG("IV Index 0x%08x Auth %s", bt_mesh.iv_index, bt_hex(sub->secure_beacon.auth, 8));

	return 0;
}

#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
static int private_random_update(void)
{
	uint8_t interval = bt_mesh_priv_beacon_update_interval_get();
	uint64_t uptime = k_uptime_get();
	int err;

	/* The Private beacon random value should change every N seconds to maintain privacy.
	 * N = (10 * interval) seconds, or on every beacon creation, if the interval is 0.
	 */
	if (bt_mesh_priv_beacon_get() == BT_MESH_FEATURE_ENABLED &&
	    interval &&
	    uptime - priv_random.timestamp < (10 * interval * MSEC_PER_SEC) &&
	    priv_random.timestamp != 0) {
		/* Not time yet */
		return 0;
	}

	err = bt_rand(priv_random.val, sizeof(priv_random.val));
	if (err) {
		return err;
	}

	/* Update the index to indicate to all subnets that the private beacon must be regenerated.
	 * Each subnet maintains the random index their private beacon data was generated with.
	 */
	priv_random.idx++;
	priv_random.timestamp = uptime;

	return 0;
}

static int private_beacon_update(struct bt_mesh_subnet *sub)
{
	struct bt_mesh_subnet_keys *keys = &sub->keys[SUBNET_KEY_TX_IDX(sub)];
	uint8_t flags = bt_mesh_net_flags(sub);
	int err;

	err = bt_mesh_beacon_encrypt(&keys->priv_beacon, flags, bt_mesh.iv_index,
				     priv_random.val, sub->priv_beacon_ctx.data,
				     sub->priv_beacon.auth);
	if (err) {
		LOG_ERR("Can't encrypt private beacon");
		return err;
	}

	sub->priv_beacon_ctx.idx = priv_random.idx;
	return 0;
}

static int private_beacon_create(struct bt_mesh_subnet *sub,
				 struct net_buf_simple *buf)
{
	int err;
	struct bt_mesh_adv  *adv;

	/* Refresh beacon data */
	err = private_random_update();
	if (err) {
		return err;
	}

	if (sub->priv_beacon_ctx.idx != priv_random.idx) {
		err = private_beacon_update(sub);
		if (err) {
			return err;
		}
	}

	adv = CONTAINER_OF(buf, struct bt_mesh_adv, b);
	adv->ctx.priv = 1;

	net_buf_simple_add_u8(buf, BEACON_TYPE_PRIVATE);
	net_buf_simple_add_mem(buf, priv_random.val, 13);
	net_buf_simple_add_mem(buf, sub->priv_beacon_ctx.data, 5);
	net_buf_simple_add_mem(buf, sub->priv_beacon.auth, 8);

	LOG_DBG("0x%03x", sub->net_idx);
	return 0;
}
#endif

int bt_mesh_beacon_create(struct bt_mesh_subnet *sub, struct net_buf_simple *buf, bool priv)
{
#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
	if (priv) {
		return private_beacon_create(sub, buf);
	}
#endif

	secure_beacon_create(sub, buf);
	return 0;
}

/* If the interval has passed or is within 5 seconds from now send a beacon */
#define BEACON_THRESHOLD(beacon) \
	((10 * ((beacon)->last + 1)) * MSEC_PER_SEC - (5 * MSEC_PER_SEC))

static bool secure_beacon_is_running(void)
{
	return bt_mesh_beacon_enabled() ||
	       atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_INITIATOR);
}

static int net_beacon_send(struct bt_mesh_subnet *sub, struct bt_mesh_beacon *beacon,
			    int (*beacon_create)(struct bt_mesh_subnet *sub,
						 struct net_buf_simple *buf))
{
	static const struct bt_mesh_send_cb send_cb = {
		.start = beacon_start,
		.end = beacon_complete,
	};
	uint32_t now = k_uptime_get_32();
	struct bt_mesh_adv *adv;
	uint32_t time_diff;
	uint32_t time_since_last_recv;
	int err;

	LOG_DBG("");

	time_diff = now - beacon->sent;
	time_since_last_recv = now - beacon->recv;
	if (time_diff < (600 * MSEC_PER_SEC) &&
		(time_diff < BEACON_THRESHOLD(beacon) ||
		 time_since_last_recv < (10 * MSEC_PER_SEC))) {
		return -ENOMSG;
	}

	adv = bt_mesh_adv_create(BT_MESH_ADV_BEACON, BT_MESH_ADV_TAG_LOCAL,
				 PROV_XMIT, K_NO_WAIT);
	if (!adv) {
		LOG_ERR("Unable to allocate beacon adv");
		return -ENOMEM; /* Bail out */
	}

	err = beacon_create(sub, &adv->b);
	if (!err) {
		bt_mesh_adv_send(adv, &send_cb, beacon);
	}

	bt_mesh_adv_unref(adv);

	return err;
}

static int net_beacon_for_subnet_send(struct bt_mesh_subnet *sub)
{
	int err = -ENOMSG;

	struct {
		struct bt_mesh_beacon *beacon;
		bool enabled;
		int (*create_fn)(struct bt_mesh_subnet *sub, struct net_buf_simple *buf);
	} beacons[] = {
		[0] = {
			.beacon = &sub->secure_beacon,
			.enabled = secure_beacon_is_running(),
			.create_fn = secure_beacon_create,
		},
#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
		[1] = {
			.beacon = &sub->priv_beacon,
			.enabled = bt_mesh_priv_beacon_get() == BT_MESH_FEATURE_ENABLED,
			.create_fn = private_beacon_create,
		},
#endif
	};

	for (int i = 0; i < ARRAY_SIZE(beacons); i++) {
		if (!beacons[i].enabled) {
			continue;
		}

		err = net_beacon_send(sub, beacons[i].beacon, beacons[i].create_fn);
		if (err < 0) {
			/* Bail out */
			break;
		}
	}

	return err;
}

static int unprovisioned_beacon_send(void)
{
	const struct bt_mesh_prov *prov;
	uint8_t uri_hash[16] = { 0 };
	struct bt_mesh_adv *adv;
	uint16_t oob_info;

	LOG_DBG("");

	adv = bt_mesh_adv_create(BT_MESH_ADV_BEACON, BT_MESH_ADV_TAG_LOCAL,
				 UNPROV_XMIT, K_NO_WAIT);
	if (!adv) {
		LOG_ERR("Unable to allocate beacon adv");
		return -ENOBUFS;
	}

	prov = bt_mesh_prov_get();

	net_buf_simple_add_u8(&adv->b, BEACON_TYPE_UNPROVISIONED);
	net_buf_simple_add_mem(&adv->b, prov->uuid, 16);

	if (prov->uri && bt_mesh_s1_str(prov->uri, uri_hash) == 0) {
		oob_info = prov->oob_info | BT_MESH_PROV_OOB_URI;
	} else {
		oob_info = prov->oob_info;
	}

	net_buf_simple_add_be16(&adv->b, oob_info);
	net_buf_simple_add_mem(&adv->b, uri_hash, 4);

	bt_mesh_adv_send(adv, NULL, NULL);
	bt_mesh_adv_unref(adv);

	if (prov->uri) {
		size_t len;

		adv = bt_mesh_adv_create(BT_MESH_ADV_URI, BT_MESH_ADV_TAG_LOCAL,
					 UNPROV_XMIT, K_NO_WAIT);
		if (!adv) {
			LOG_ERR("Unable to allocate URI adv");
			return -ENOBUFS;
		}

		len = strlen(prov->uri);
		if (net_buf_simple_tailroom(&adv->b) < len) {
			LOG_WRN("Too long URI to fit advertising data");
		} else {
			net_buf_simple_add_mem(&adv->b, prov->uri, len);
			bt_mesh_adv_send(adv, NULL, NULL);
		}

		bt_mesh_adv_unref(adv);
	}

	return 0;
}

static void unprovisioned_beacon_recv(struct net_buf_simple *buf)
{
	const struct bt_mesh_prov *prov;
	uint8_t *uuid;
	uint16_t oob_info;
	uint32_t uri_hash_val;
	uint32_t *uri_hash = NULL;

	prov = bt_mesh_prov_get();

	if (!prov->unprovisioned_beacon) {
		return;
	}

	if (buf->len != 18 && buf->len != 22) {
		LOG_ERR("Invalid unprovisioned beacon length (%u)", buf->len);
		return;
	}

	uuid = net_buf_simple_pull_mem(buf, 16);
	oob_info = net_buf_simple_pull_be16(buf);

	if (buf->len == 4) {
		uri_hash_val = net_buf_simple_pull_be32(buf);
		uri_hash = &uri_hash_val;
	}

	LOG_DBG("uuid %s", bt_hex(uuid, 16));

	prov->unprovisioned_beacon(uuid,
				   (bt_mesh_prov_oob_info_t)oob_info,
				   uri_hash);
}

static void sub_update_beacon_observation(struct bt_mesh_subnet *sub)
{
	sub->secure_beacon.last = sub->secure_beacon.cur;
	sub->secure_beacon.cur = 0U;

#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
	sub->priv_beacon.last = sub->priv_beacon.cur;
	sub->priv_beacon.cur = 0U;
#endif
}

static void update_beacon_observation(void)
{
	static bool first_half;

	/* Observation period is 20 seconds, whereas the beacon timer
	 * runs every 10 seconds. We process what's happened during the
	 * window only after the second half.
	 */
	first_half = !first_half;
	if (first_half) {
		return;
	}

	bt_mesh_subnet_foreach(sub_update_beacon_observation);
}

static bool net_beacon_is_running(void)
{
	return secure_beacon_is_running() ||
	       (bt_mesh_priv_beacon_get() == BT_MESH_FEATURE_ENABLED);
}

static bool beacons_send_next(void)
{
	int err;
	struct bt_mesh_subnet *sub_first = bt_mesh_subnet_next(NULL);
	struct bt_mesh_subnet *sub_next;

	do {
		sub_next = bt_mesh_subnet_next(beacon_send_sub_curr);
		if (sub_next == sub_first && beacon_send_sub_curr != NULL) {
			beacon_send_sub_curr = NULL;
			return false;
		}

		beacon_send_sub_curr = sub_next;
		err = net_beacon_for_subnet_send(beacon_send_sub_curr);
		if (err < 0 && (err != -ENOMSG)) {
			LOG_ERR("Failed to advertise subnet %d: err %d",
				beacon_send_sub_curr->net_idx, err);
		}
	} while (err);

	return true;
}

static void beacon_send(struct k_work *work)
{
	LOG_DBG("");

	if (bt_mesh_is_provisioned()) {
		if (!net_beacon_is_running()) {
			return;
		}

		if (!beacon_send_sub_curr) {
			update_beacon_observation();
		}

		if (!beacons_send_next()) {
			k_work_schedule(&beacon_timer, PROVISIONED_INTERVAL);
		}

		return;
	}

	if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) {
		/* Don't send anything if we have an active provisioning link */
		if (!bt_mesh_prov_active()) {
			unprovisioned_beacon_send();
		}

		k_work_schedule(&beacon_timer, K_SECONDS(CONFIG_BT_MESH_UNPROV_BEACON_INT));
	}
}

static bool auth_match(struct bt_mesh_subnet_keys *keys,
		       const struct beacon_params *params)
{
	uint8_t net_auth[8];

	if (memcmp(params->net_id, keys->net_id, 8)) {
		return false;
	}

	if (bt_mesh_beacon_auth(&keys->beacon, params->flags, keys->net_id, params->iv_index,
				net_auth)) {
		return false;
	}

	if (memcmp(params->auth, net_auth, 8)) {
		LOG_WRN("Invalid auth value. Received auth: %s", bt_hex(params->auth, 8));
		LOG_WRN("Calculated auth: %s", bt_hex(net_auth, 8));
		return false;
	}

	return true;
}

static bool secure_beacon_authenticate(struct bt_mesh_subnet *sub, void *cb_data)
{
	struct beacon_params *params = cb_data;

	for (int i = 0; i < ARRAY_SIZE(sub->keys); i++) {
		if (sub->keys[i].valid && auth_match(&sub->keys[i], params)) {
			params->new_key = (i > 0);
#if defined(CONFIG_BT_TESTING)
			struct bt_mesh_snb beacon_info;

			beacon_info.net_idx = sub->net_idx;
			beacon_info.flags = params->flags;
			memcpy(&beacon_info.net_id, params->net_id, 8);
			beacon_info.iv_idx = params->iv_index;
			memcpy(&beacon_info.auth_val, params->auth, 8);

#if (CONFIG_MESH_CB_REGISTERED)
			struct bt_mesh_beacon_cb *p_cur_beacon_cb = p_beacon_list;

			while(p_cur_beacon_cb) {
				if (p_cur_beacon_cb->snb_received) {
					p_cur_beacon_cb->snb_received(&beacon_info);
				}
				p_cur_beacon_cb = p_cur_beacon_cb->next;
			}
#else
			struct bt_mesh_beacon_cb *p_cur_beacon_cb = (struct bt_mesh_beacon_cb *)_beacon_cbs;

			while(p_cur_beacon_cb != (struct bt_mesh_beacon_cb *)_ebeacon_cbs) {
				if (p_cur_beacon_cb->snb_received) {
					p_cur_beacon_cb->snb_received(&beacon_info);
				}
				p_cur_beacon_cb++;
			}
#endif
#endif

			return true;
		}
	}

	return false;
}

static bool priv_beacon_decrypt(struct bt_mesh_subnet *sub, void *cb_data)
{
	struct beacon_params *params = cb_data;
	uint8_t out[5];
	int err;

	for (int i = 0; i < ARRAY_SIZE(sub->keys); i++) {
		if (!sub->keys[i].valid) {
			continue;
		}

		err = bt_mesh_beacon_decrypt(&sub->keys[i].priv_beacon, params->random,
					     params->data, params->auth, out);
		if (!err) {
			params->new_key = (i > 0);
			params->flags = out[0];
			params->iv_index = sys_get_be32(&out[1]);

#if defined(CONFIG_BT_TESTING)
			struct bt_mesh_prb beacon_info;

			beacon_info.net_idx = sub->net_idx;
			memcpy(beacon_info.random, params->random, 13);
			beacon_info.flags = params->flags;
			beacon_info.iv_idx = params->iv_index;
			memcpy(&beacon_info.auth_tag, params->auth, 8);

#if (CONFIG_MESH_CB_REGISTERED)
			struct bt_mesh_beacon_cb *p_cur_beacon_cb = p_beacon_list;

			while(p_cur_beacon_cb) {
				if (p_cur_beacon_cb->priv_received) {
					p_cur_beacon_cb->priv_received(&beacon_info);
				}
				p_cur_beacon_cb = p_cur_beacon_cb->next;
			}
#else
			struct bt_mesh_beacon_cb *p_cur_beacon_cb = (struct bt_mesh_beacon_cb *)_beacon_cbs;

			while(p_cur_beacon_cb != (struct bt_mesh_beacon_cb *)_ebeacon_cbs) {
				if (p_cur_beacon_cb->priv_received) {
					p_cur_beacon_cb->priv_received(&beacon_info);
				}
				p_cur_beacon_cb++;
			}
#endif
#endif
			return true;
		}
	}

	return false;
}

static void net_beacon_register(struct bt_mesh_beacon *beacon, bool priv)
{
	if (((priv && bt_mesh_priv_beacon_get() == BT_MESH_PRIV_GATT_PROXY_ENABLED) ||
	     bt_mesh_beacon_enabled()) && beacon->cur < 0xff) {
		beacon->cur++;
		beacon->recv = k_uptime_get_32();
	}
}

static void net_beacon_recv(struct bt_mesh_subnet *sub,
			    const struct beacon_params *params)
{
	bt_mesh_kr_update(sub, BT_MESH_KEY_REFRESH(params->flags),
			  params->new_key);

	/* If we have NetKey0 accept IV index initiation only from it */
	if (bt_mesh_subnet_get(BT_MESH_KEY_PRIMARY) &&
	    sub->net_idx != BT_MESH_KEY_PRIMARY) {
		LOG_WRN("Ignoring secure beacon on non-primary subnet");
		return;
	}

	LOG_DBG("net_idx 0x%04x flags %u iv_index 0x%08x, "
		"current iv_index 0x%08x",
		sub->net_idx, params->flags, params->iv_index, bt_mesh.iv_index);

	if (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_INITIATOR) &&
	    (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS) ==
	     BT_MESH_IV_UPDATE(params->flags))) {
		bt_mesh_beacon_ivu_initiator(false);
	}

	bt_mesh_net_iv_update(params->iv_index,
			      BT_MESH_IV_UPDATE(params->flags));
}

static void net_beacon_resolve(struct beacon_params *params,
			       bool (*matcher)(struct bt_mesh_subnet *sub,
					       void *cb_data))
{
	struct bt_mesh_subnet *sub;
	struct bt_mesh_beacon *beacon;

	sub = bt_mesh_subnet_find(beacon_cache_match, (void *)params);
	if (sub) {
		beacon = subnet_beacon_get_by_type(sub, params->private);

		/* We've seen this beacon before - just update the stats */
		net_beacon_register(beacon, params->private);
		return;
	}

	sub = bt_mesh_subnet_find(matcher, params);
	if (!sub) {
		LOG_DBG("No subnet that matched beacon");
		return;
	}

	if (sub->kr_phase == BT_MESH_KR_PHASE_2 && !params->new_key) {
		LOG_WRN("Ignoring Phase 2 KR Update secured using old key");
		return;
	}

	beacon = subnet_beacon_get_by_type(sub, params->private);

	cache_add(params->auth, beacon);

	net_beacon_recv(sub, params);
	net_beacon_register(beacon, params->private);
}

static void secure_beacon_recv(struct net_buf_simple *buf)
{
	struct beacon_params params;

	if (buf->len < 21) {
		LOG_ERR("Too short secure beacon (len %u)", buf->len);
		return;
	}

	params.private = false;
	params.flags = net_buf_simple_pull_u8(buf);
	params.net_id = net_buf_simple_pull_mem(buf, 8);
	params.iv_index = net_buf_simple_pull_be32(buf);
	params.auth = buf->data;

	net_beacon_resolve(&params, secure_beacon_authenticate);
}

static void private_beacon_recv(struct net_buf_simple *buf)
{
	struct beacon_params params;

	if (buf->len < 26) {
		LOG_ERR("Too short private beacon (len %u)", buf->len);
		return;
	}

	params.private = true;
	params.random = net_buf_simple_pull_mem(buf, 13);
	params.data = net_buf_simple_pull_mem(buf, 5);
	params.auth = buf->data;

	net_beacon_resolve(&params, priv_beacon_decrypt);
}

void bt_mesh_beacon_recv(struct net_buf_simple *buf)
{
	uint8_t type;

	LOG_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len));

	if (buf->len < 1) {
		LOG_ERR("Too short beacon");
		return;
	}

	type = net_buf_simple_pull_u8(buf);
	switch (type) {
	case BEACON_TYPE_UNPROVISIONED:
		if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) {
			unprovisioned_beacon_recv(buf);
		}
		break;
	case BEACON_TYPE_SECURE:
		secure_beacon_recv(buf);
		break;
	case BEACON_TYPE_PRIVATE:
		private_beacon_recv(buf);
		break;
	default:
		LOG_WRN("Unknown beacon type 0x%02x", type);
		break;
	}
}

void bt_mesh_beacon_update(struct bt_mesh_subnet *sub)
{
	uint8_t flags = bt_mesh_net_flags(sub);
	struct bt_mesh_subnet_keys *keys;

	keys = &sub->keys[SUBNET_KEY_TX_IDX(sub)];

	LOG_DBG("NetIndex 0x%03x Using %s key", sub->net_idx,
		SUBNET_KEY_TX_IDX(sub) ? "new" : "current");
	LOG_DBG("flags 0x%02x, IVI 0x%08x", flags, bt_mesh.iv_index);

#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
	/* Invalidate private beacon to force regeneration: */
	sub->priv_beacon_ctx.idx = priv_random.idx - 1;
	priv_random.timestamp = 0;
#endif

	bt_mesh_beacon_auth(&keys->beacon, flags, keys->net_id, bt_mesh.iv_index,
			    sub->secure_beacon.auth);
}

static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt)
{
	if (evt != BT_MESH_KEY_DELETED) {
		bt_mesh_beacon_update(sub);
	}
}

#if (CONFIG_MESH_CB_REGISTERED)
static struct bt_mesh_subnet_cb  bt_mesh_subnet_cb_beacon = {
	.evt_handler = subnet_evt,
	.next = NULL,
};
#else
BT_MESH_SUBNET_CB_DEFINE(beacon) = {
	.evt_handler = subnet_evt,
};
#endif

void bt_mesh_beacon_init(void)
{
	k_work_init_delayable(&beacon_timer, beacon_send);

#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
	private_random_update();
#endif
}

void bt_mesh_beacon_ivu_initiator(bool enable)
{
	atomic_set_bit_to(bt_mesh.flags, BT_MESH_IVU_INITIATOR, enable);

	/* Fire the beacon handler straight away if it's not already pending -
	 * in which case we'll fire according to the ongoing periodic sending.
	 * If beacons are disabled, the handler will exit early.
	 *
	 * An alternative solution would be to check whether beacons are enabled
	 * here, and cancel if not. As the cancel operation may fail, we would
	 * still have to implement an early exit mechanism, so we might as well
	 * just use this every time.
	 */
	beacon_send_sub_curr = NULL;
	k_work_schedule(&beacon_timer, K_NO_WAIT);
}

static void subnet_beacon_enable(struct bt_mesh_subnet *sub)
{
	sub->secure_beacon.last = 0U;
	sub->secure_beacon.cur = 0U;

#if defined(CONFIG_BT_MESH_PRIV_BEACONS)
	sub->priv_beacon.last = 0U;
	sub->priv_beacon.cur = 0U;
#endif

	bt_mesh_beacon_update(sub);
}

void bt_mesh_beacon_enable(void)
{
	if (bt_mesh_is_provisioned()) {
		bt_mesh_subnet_foreach(subnet_beacon_enable);
	}

	beacon_send_sub_curr = NULL;
	k_work_reschedule(&beacon_timer, K_NO_WAIT);
}

void bt_mesh_beacon_disable(void)
{
	/* If this fails, we'll do an early exit in the work handler. */
	beacon_send_sub_curr = NULL;
	(void)k_work_cancel_delayable(&beacon_timer);
}

#if (CONFIG_MESH_CB_REGISTERED)
void bt_mesh_beacon_subnet_cb_init(void)
{
	bt_mesh_subnet_cb_register(&bt_mesh_subnet_cb_beacon);
}
#endif
