tests: Bluetooth: Mesh: Add tests to cover message publication

This commit adds 3 tests that check:
- Publish period state
- Publish retransmit count and interval steps states
- Canceling periodic publication and publication retransmission

Signed-off-by: Pavel Vasilyev <pavel.vasilyev@nordicsemi.no>
diff --git a/tests/bluetooth/bsim_bt/bsim_test_mesh/prj.conf b/tests/bluetooth/bsim_bt/bsim_test_mesh/prj.conf
index 8693d70..dfc51ad 100644
--- a/tests/bluetooth/bsim_bt/bsim_test_mesh/prj.conf
+++ b/tests/bluetooth/bsim_bt/bsim_test_mesh/prj.conf
@@ -1,10 +1,10 @@
-CONFIG_BT=y
-CONFIG_BT_DEBUG_LOG=y
 CONFIG_LOG_MODE_IMMEDIATE=y
 CONFIG_ASSERT=y
+CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000
 
 # Bluetooth configuration
 CONFIG_BT=y
+CONFIG_BT_DEBUG_LOG=y
 CONFIG_BT_PRIVACY=n
 CONFIG_BT_COMPANY_ID=0x0059
 CONFIG_BT_DEVICE_NAME="Mesh test"
diff --git a/tests/bluetooth/bsim_bt/bsim_test_mesh/src/test_access.c b/tests/bluetooth/bsim_bt/bsim_test_mesh/src/test_access.c
index 4ee49f2..1110932 100644
--- a/tests/bluetooth/bsim_bt/bsim_test_mesh/src/test_access.c
+++ b/tests/bluetooth/bsim_bt/bsim_test_mesh/src/test_access.c
@@ -24,6 +24,11 @@
 #define TEST_MESSAGE_OP_1  BT_MESH_MODEL_OP_1(0x11)
 #define TEST_MESSAGE_OP_2  BT_MESH_MODEL_OP_1(0x12)
 #define TEST_MESSAGE_OP_3  BT_MESH_MODEL_OP_1(0x13)
+#define TEST_MESSAGE_OP_4  BT_MESH_MODEL_OP_1(0x14)
+
+#define PUB_PERIOD_COUNT 3
+#define RX_JITTER_MAX (10 + CONFIG_BT_MESH_NETWORK_TRANSMIT_COUNT * \
+		       (CONFIG_BT_MESH_NETWORK_TRANSMIT_INTERVAL + 10))
 
 static int model1_init(struct bt_mesh_model *model);
 static int model2_init(struct bt_mesh_model *model);
@@ -39,6 +44,82 @@
 static uint8_t net_key[16] = { 0xcc };
 static struct bt_mesh_prov prov;
 
+/* Test vector for periodic publication tests. */
+static const struct {
+	uint8_t period;
+	uint8_t div;
+	int32_t period_ms;
+} test_period[] = {
+	{ BT_MESH_PUB_PERIOD_100MS(5), 0, 500 },
+	{ BT_MESH_PUB_PERIOD_SEC(2),   0, 2000 },
+	{ BT_MESH_PUB_PERIOD_10SEC(1), 0, 10000 },
+	{ BT_MESH_PUB_PERIOD_SEC(3),   1, 1500 },
+	{ BT_MESH_PUB_PERIOD_10SEC(3), 3, 3750 },
+};
+
+/* Test vector for publication retransmissions tests. */
+static const uint8_t test_transmit[] = {
+	BT_MESH_PUB_TRANSMIT(4, 50),
+	BT_MESH_PUB_TRANSMIT(3, 100),
+	BT_MESH_PUB_TRANSMIT(2, 200),
+};
+
+/* Test vector for canceling a message publication. */
+static const struct {
+	uint8_t period;
+	uint8_t transmit;
+	uint8_t msgs;
+	int32_t sleep;
+	int32_t duration;
+} test_cancel[] = {
+	/* Test cancelling periodic publication. */
+	{
+		BT_MESH_PUB_PERIOD_SEC(2), 0, 2,
+		2000 /* period */ + 100 /* margin */,
+		3 /* messages */ * 2000 /* period */
+	},
+	/* Test cancelling publication retransmission. */
+	{
+		BT_MESH_PUB_PERIOD_SEC(3), BT_MESH_PUB_TRANSMIT(3, 200), 3,
+		200 /* retransmission interval */ + 50 /* margin */,
+		3000 /* one period */
+	},
+};
+
+static struct k_sem publish_sem;
+static bool publish_allow;
+
+static int model1_update(struct bt_mesh_model *model)
+{
+	model->pub->msg->data[1]++;
+
+	LOG_DBG("New pub: n: %d t: %d", model->pub->msg->data[1], k_uptime_get_32());
+
+	return publish_allow ? k_sem_give(&publish_sem), 0 : -1;
+}
+
+static int test_msg4_handler(struct bt_mesh_model *model,
+			     struct bt_mesh_msg_ctx *ctx,
+			     struct net_buf_simple *buf)
+{
+	static uint8_t prev_num;
+	uint8_t num = net_buf_simple_pull_u8(buf);
+
+	LOG_DBG("Recv msg: n: %d t: %u", num, k_uptime_get_32());
+
+	/* Ensure that payload changes. */
+	ASSERT_TRUE(prev_num != num);
+	prev_num = num;
+
+	k_sem_give(&publish_sem);
+	return 0;
+}
+
+static struct bt_mesh_model_pub model_pub1 = {
+	.msg = NET_BUF_SIMPLE(BT_MESH_TX_SDU_MAX),
+	.update = model1_update,
+};
+
 static const struct bt_mesh_model_cb test_model1_cb = {
 	.init = model1_init,
 };
@@ -53,6 +134,7 @@
 
 static const struct bt_mesh_model_op model_op1[] = {
 	{ TEST_MESSAGE_OP_1, 0, test_msg_handler },
+	{ TEST_MESSAGE_OP_4, 0, test_msg4_handler },
 	BT_MESH_MODEL_OP_END
 };
 
@@ -71,7 +153,7 @@
 static struct bt_mesh_model models[] = {
 	BT_MESH_MODEL_CFG_SRV,
 	BT_MESH_MODEL_CFG_CLI(&cfg_cli),
-	BT_MESH_MODEL_CB(TEST_MODEL_ID_1, model_op1, NULL, NULL, &test_model1_cb),
+	BT_MESH_MODEL_CB(TEST_MODEL_ID_1, model_op1, &model_pub1, NULL, &test_model1_cb),
 	BT_MESH_MODEL_CB(TEST_MODEL_ID_2, model_op2, NULL, NULL, &test_model2_cb),
 	BT_MESH_MODEL_CB(TEST_MODEL_ID_3, model_op3, NULL, NULL, &test_model3_cb),
 };
@@ -245,6 +327,336 @@
 	PASS();
 }
 
+static void pub_param_set(uint8_t period, uint8_t transmit)
+{
+	struct bt_mesh_cfg_mod_pub pub_params = {
+		.addr = UNICAST_ADDR2,
+		.uuid = NULL,
+		.cred_flag = false,
+		.app_idx = 0,
+		.ttl = 5,
+		.period = period,
+		.transmit = transmit,
+	};
+	uint8_t status;
+	int err;
+
+	err = bt_mesh_cfg_mod_pub_set(0, UNICAST_ADDR1, UNICAST_ADDR1, TEST_MODEL_ID_1,
+				      &pub_params, &status);
+	if (err || status) {
+		FAIL("Mod pub set failed (err %d, status %u)", err, status);
+	}
+}
+
+static void msg4_publish(void)
+{
+	struct bt_mesh_model *model = &models[2];
+
+	bt_mesh_model_msg_init(model->pub->msg, TEST_MESSAGE_OP_4);
+	net_buf_simple_add_u8(model->pub->msg, 1);
+	bt_mesh_model_publish(model);
+}
+
+static void pub_jitter_check(int32_t interval, uint8_t count)
+{
+	int64_t timestamp = k_uptime_get();
+	int32_t jitter = 0;
+	int err;
+
+	for (size_t j = 0; j < count; j++) {
+		/* Every new publication will release semaphore in the update handler and the time
+		 * between two consecutive publications will be measured.
+		 */
+		err = k_sem_take(&publish_sem, K_SECONDS(20));
+		if (err) {
+			FAIL("Send timed out");
+		}
+
+		int32_t time_delta = k_uptime_delta(&timestamp);
+		int32_t pub_delta = llabs(time_delta - interval);
+
+		jitter = MAX(pub_delta, jitter);
+
+		LOG_DBG("Send time: %d delta: %d jitter: %d", (int32_t)timestamp, time_delta,
+			jitter);
+	}
+
+	LOG_INF("Send jitter: %d", jitter);
+	ASSERT_TRUE(jitter <= 10);
+}
+
+static void recv_jitter_check(int32_t interval, uint8_t count)
+{
+	int64_t timestamp;
+	uint32_t jitter = 0;
+	int err;
+
+	/* The measurment starts by the first received message. */
+	err = k_sem_take(&publish_sem, K_SECONDS(20));
+	if (err) {
+		FAIL("Recv timed out");
+	}
+
+	timestamp = k_uptime_get();
+
+	for (size_t j = 0; j < count; j++) {
+		/* Every new received message will release semaphore in the message handler and
+		 * the time between two consecutive publications will be measured.
+		 */
+		err = k_sem_take(&publish_sem, K_SECONDS(20));
+		if (err) {
+			FAIL("Recv timed out");
+		}
+
+		int32_t time_delta = k_uptime_delta(&timestamp);
+		int32_t pub_delta = llabs(time_delta - interval);
+
+		jitter = MAX(pub_delta, jitter);
+
+		LOG_DBG("Recv time: %d delta: %d jitter: %d", (int32_t)timestamp, time_delta,
+			jitter);
+	}
+
+	LOG_INF("Recv jitter: %d", jitter);
+	ASSERT_TRUE(jitter <= RX_JITTER_MAX);
+}
+
+/* Test publish period states by publishing a message and checking interval between update handler
+ * calls.
+ */
+static void test_tx_period(void)
+{
+	struct bt_mesh_model *model = &models[2];
+
+	bt_mesh_test_cfg_set(NULL, 60);
+	bt_mesh_device_setup(&prov, &local_comp);
+	provision(UNICAST_ADDR1);
+	common_configure(UNICAST_ADDR1);
+
+	k_sem_init(&publish_sem, 0, 1);
+
+	for (size_t i = 0; i < ARRAY_SIZE(test_period); i++) {
+		pub_param_set(test_period[i].period, 0);
+
+		model->pub->fast_period = test_period[i].div > 0;
+		model->pub->period_div = test_period[i].div;
+
+		LOG_INF("Publication period: %d", test_period[i].period_ms);
+
+		/* Start publishing messages and measure jitter. */
+		msg4_publish();
+		publish_allow = true;
+		pub_jitter_check(test_period[i].period_ms, PUB_PERIOD_COUNT);
+
+		/* Disable periodic publication before the next test iteration. */
+		publish_allow = false;
+
+		/* Let the receiver hit the first semaphore. */
+		k_sleep(K_SECONDS(1));
+	}
+
+	PASS();
+}
+
+/* Receive a periodically published message and check publication period by measuring interval
+ * between message handler calls.
+ */
+static void test_rx_period(void)
+{
+	bt_mesh_test_cfg_set(NULL, 60);
+	bt_mesh_device_setup(&prov, &local_comp);
+	provision(UNICAST_ADDR2);
+	common_configure(UNICAST_ADDR2);
+
+	k_sem_init(&publish_sem, 0, 1);
+
+	for (size_t i = 0; i < ARRAY_SIZE(test_period); i++) {
+		recv_jitter_check(test_period[i].period_ms, PUB_PERIOD_COUNT);
+	}
+
+	PASS();
+}
+
+/* Test publish retransmit interval and count states by publishing a message and checking interval
+ * between update handler calls.
+ */
+static void test_tx_transmit(void)
+{
+	struct bt_mesh_model *model = &models[2];
+	uint8_t status;
+	int err;
+
+	bt_mesh_test_cfg_set(NULL, 60);
+	bt_mesh_device_setup(&prov, &local_comp);
+	provision(UNICAST_ADDR1);
+	common_configure(UNICAST_ADDR1);
+
+	k_sem_init(&publish_sem, 0, 1);
+
+	/* Network retransmissions has to be disabled so that the legacy advertiser sleeps for the
+	 * least possible time, which is 50ms. This will let the access layer publish a message
+	 * with 50ms retransmission interval.
+	 */
+	err = bt_mesh_cfg_net_transmit_set(0, UNICAST_ADDR1,
+				BT_MESH_TRANSMIT(0, CONFIG_BT_MESH_NETWORK_TRANSMIT_INTERVAL),
+				&status);
+	if (err || status != BT_MESH_TRANSMIT(0, CONFIG_BT_MESH_NETWORK_TRANSMIT_INTERVAL)) {
+		FAIL("Net transmit set failed (err %d, status %u)", err,
+		     status);
+	}
+
+	publish_allow = true;
+	model->pub->retr_update = true;
+
+	for (size_t i = 0; i < ARRAY_SIZE(test_transmit); i++) {
+		pub_param_set(0, test_transmit[i]);
+
+		int32_t interval = BT_MESH_PUB_TRANSMIT_INT(test_transmit[i]);
+		int count = BT_MESH_PUB_TRANSMIT_COUNT(test_transmit[i]);
+
+		LOG_INF("Retransmission interval: %d, count: %d", interval, count);
+
+		/* Start publishing messages and measure jitter. */
+		msg4_publish();
+		pub_jitter_check(interval, count);
+
+		/* Let the receiver hit the first semaphore. */
+		k_sleep(K_SECONDS(1));
+	}
+
+	PASS();
+}
+
+/* Receive a published message and check retransmission interval by measuring interval between
+ * message handler calls.
+ */
+static void test_rx_transmit(void)
+{
+	bt_mesh_test_cfg_set(NULL, 60);
+	bt_mesh_device_setup(&prov, &local_comp);
+	provision(UNICAST_ADDR2);
+	common_configure(UNICAST_ADDR2);
+
+	k_sem_init(&publish_sem, 0, 1);
+
+	for (size_t i = 0; i < ARRAY_SIZE(test_transmit); i++) {
+		int32_t interval = BT_MESH_PUB_TRANSMIT_INT(test_transmit[i]);
+		int count = BT_MESH_PUB_TRANSMIT_COUNT(test_transmit[i]);
+
+		recv_jitter_check(interval, count);
+	}
+
+	PASS();
+}
+
+/* Cancel one of messages to be published and check that the next one is published when next period
+ * starts.
+ */
+static void test_tx_cancel(void)
+{
+	struct bt_mesh_model *model = &models[2];
+	int err;
+
+	bt_mesh_test_cfg_set(NULL, 20);
+	bt_mesh_device_setup(&prov, &local_comp);
+	provision(UNICAST_ADDR1);
+	common_configure(UNICAST_ADDR1);
+
+	k_sem_init(&publish_sem, 0, 1);
+
+	model->pub->retr_update = true;
+
+	for (size_t i = 0; i < ARRAY_SIZE(test_cancel); i++) {
+		pub_param_set(test_cancel[i].period, test_cancel[i].transmit);
+
+		msg4_publish();
+		publish_allow = true;
+		int64_t timestamp = k_uptime_get();
+
+		/* Send few messages except one that is to be cancelled. */
+		for (size_t j = 0; j < test_cancel[i].msgs - 1; j++) {
+			err = k_sem_take(&publish_sem, K_SECONDS(20));
+			if (err) {
+				FAIL("Send timed out");
+			}
+		}
+
+		/* Cancel the next publication. */
+		publish_allow = false;
+		k_sleep(K_MSEC(test_cancel[i].sleep));
+
+		/* Reenable publication a wait for a next message to be published. */
+		publish_allow = true;
+		err = k_sem_take(&publish_sem, K_SECONDS(20));
+		if (err) {
+			FAIL("Send timed out");
+		}
+
+		/* Disable periodic publication before the next test iteration. */
+		publish_allow = false;
+
+		/* If the canceled message is also sent, the semaphore will be released earlier than
+		 * expected.
+		 */
+		int32_t time_delta = k_uptime_delta(&timestamp);
+		int32_t jitter = llabs(time_delta - test_cancel[i].duration);
+
+		LOG_DBG("Send time: %d delta: %d", (int32_t)timestamp, time_delta);
+		LOG_INF("Send jitter: %d", jitter);
+		ASSERT_TRUE(jitter <= 10);
+
+		/* Let the receiver hit the first semaphore. */
+		k_sleep(K_SECONDS(1));
+	}
+
+	PASS();
+}
+
+/* Receive all published messages and ensure that cancelled message is not received. */
+static void test_rx_cancel(void)
+{
+	bt_mesh_test_cfg_set(NULL, 20);
+	bt_mesh_device_setup(&prov, &local_comp);
+	provision(UNICAST_ADDR2);
+	common_configure(UNICAST_ADDR2);
+
+	k_sem_init(&publish_sem, 0, 1);
+
+	for (size_t i = 0; i < ARRAY_SIZE(test_cancel); i++) {
+		int64_t timestamp;
+		int err;
+
+		/* Wait for the first published message. */
+		err = k_sem_take(&publish_sem, K_SECONDS(20));
+		if (err) {
+			FAIL("Recv timed out");
+		}
+
+		timestamp = k_uptime_get();
+
+		/* Wait for the rest messages to be published (incl. the next after cancelled one).
+		 */
+		for (size_t j = 0; j < test_cancel[i].msgs; j++) {
+			err = k_sem_take(&publish_sem, K_SECONDS(20));
+			if (err) {
+				FAIL("Recv timed out");
+			}
+		}
+
+		/* If the canceled message is received, the semaphore will be released earlier than
+		 * expected.
+		 */
+		int32_t time_delta = k_uptime_delta(&timestamp);
+		int32_t jitter = llabs(time_delta - test_cancel[i].duration);
+
+		LOG_DBG("Recv time: %d delta: %d", (int32_t)timestamp, time_delta);
+		LOG_INF("Recv jitter: %d", jitter);
+		ASSERT_TRUE(jitter <= RX_JITTER_MAX);
+	}
+
+	PASS();
+}
+
 #define TEST_CASE(role, name, description)                     \
 	{                                                      \
 		.test_id = "access_" #role "_" #name,          \
@@ -255,8 +667,14 @@
 
 static const struct bst_test_instance test_access[] = {
 	TEST_CASE(tx, ext_model, "Access: tx data of extended models"),
-
 	TEST_CASE(sub, ext_model, "Access: data subscription of extended models"),
+	TEST_CASE(tx, period, "Access: Publish a message periodically"),
+	TEST_CASE(rx, period, "Access: Receive periodically published message"),
+	TEST_CASE(tx, transmit, "Access: Publish and retransmit message"),
+	TEST_CASE(rx, transmit, "Access: Receive retransmitted messages"),
+	TEST_CASE(tx, cancel, "Access: Cancel a message during publication"),
+	TEST_CASE(rx, cancel, "Access: Receive published messages except cancelled"),
+
 	BSTEST_END_MARKER
 };
 
diff --git a/tests/bluetooth/bsim_bt/bsim_test_mesh/tests_scripts/access/access_cancel.sh b/tests/bluetooth/bsim_bt/bsim_test_mesh/tests_scripts/access/access_cancel.sh
new file mode 100755
index 0000000..6cca2c8
--- /dev/null
+++ b/tests/bluetooth/bsim_bt/bsim_test_mesh/tests_scripts/access/access_cancel.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+# Copyright 2021 Nordic Semiconductor
+# SPDX-License-Identifier: Apache-2.0
+
+source $(dirname "${BASH_SOURCE[0]}")/../../_mesh_test.sh
+
+RunTest mesh_access_publication_cancel \
+	access_tx_cancel access_rx_cancel
diff --git a/tests/bluetooth/bsim_bt/bsim_test_mesh/tests_scripts/access/access_period.sh b/tests/bluetooth/bsim_bt/bsim_test_mesh/tests_scripts/access/access_period.sh
new file mode 100755
index 0000000..39fd12e
--- /dev/null
+++ b/tests/bluetooth/bsim_bt/bsim_test_mesh/tests_scripts/access/access_period.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+# Copyright 2021 Nordic Semiconductor
+# SPDX-License-Identifier: Apache-2.0
+
+source $(dirname "${BASH_SOURCE[0]}")/../../_mesh_test.sh
+
+RunTest mesh_access_periodic_publication \
+	access_tx_period access_rx_period
diff --git a/tests/bluetooth/bsim_bt/bsim_test_mesh/tests_scripts/access/access_transmit.sh b/tests/bluetooth/bsim_bt/bsim_test_mesh/tests_scripts/access/access_transmit.sh
new file mode 100755
index 0000000..b3dcd56
--- /dev/null
+++ b/tests/bluetooth/bsim_bt/bsim_test_mesh/tests_scripts/access/access_transmit.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+# Copyright 2021 Nordic Semiconductor
+# SPDX-License-Identifier: Apache-2.0
+
+source $(dirname "${BASH_SOURCE[0]}")/../../_mesh_test.sh
+
+RunTest mesh_access_publication_retransmission \
+	access_tx_transmit access_rx_transmit