net: coap: Rework pending retransmission logic

Introduce retransmission counter to the coap_pending structure. This
allows to simplify the retransmission logic and allows to keep track of
the number of remaining retranmissions.

Additionally, extend the `coap_pending_init()` function with `retries`
parameter, which allows to set the retransmission count individually for
each confirmable transaction.

Fixes #28117

Signed-off-by: Robert Lubos <robert.lubos@nordicsemi.no>
diff --git a/doc/releases/release-notes-2.5.rst b/doc/releases/release-notes-2.5.rst
index 7d95723..fbb687c 100644
--- a/doc/releases/release-notes-2.5.rst
+++ b/doc/releases/release-notes-2.5.rst
@@ -58,6 +58,10 @@
   timeout usage must use the new-style k_timeout_t type and not the
   legacy/deprecated millisecond counts.
 
+* The :c:func:`coap_pending_init` function now accepts an additional ``retries``
+  parameter, allowing to specify the maximum retransmission count of the
+  confirmable message.
+
 Deprecated in this release
 ==========================
 
diff --git a/include/net/coap.h b/include/net/coap.h
index 32bc989..91e3b31 100644
--- a/include/net/coap.h
+++ b/include/net/coap.h
@@ -233,6 +233,8 @@
 			    struct coap_reply *reply,
 			    const struct sockaddr *from);
 
+#define COAP_DEFAULT_MAX_RETRANSMIT 4
+
 /**
  * @brief Represents a request awaiting for an acknowledgment (ACK).
  */
@@ -243,6 +245,7 @@
 	uint16_t id;
 	uint8_t *data;
 	uint16_t len;
+	uint8_t retries;
 };
 
 /**
@@ -687,12 +690,14 @@
  * confirmation message, initialized with data from @a request
  * @param request Message waiting for confirmation
  * @param addr Address to send the retransmission
+ * @param retries Maximum number of retransmissions of the message.
  *
  * @return 0 in case of success or negative in case of error.
  */
 int coap_pending_init(struct coap_pending *pending,
 		      const struct coap_packet *request,
-		      const struct sockaddr *addr);
+		      const struct sockaddr *addr,
+		      uint8_t retries);
 
 /**
  * @brief Returns the next available pending struct, that can be used
diff --git a/samples/net/sockets/coap_server/src/coap-server.c b/samples/net/sockets/coap_server/src/coap-server.c
index 2341618..adc5be9 100644
--- a/samples/net/sockets/coap_server/src/coap-server.c
+++ b/samples/net/sockets/coap_server/src/coap-server.c
@@ -995,7 +995,8 @@
 		return -ENOMEM;
 	}
 
-	r = coap_pending_init(pending, response, addr);
+	r = coap_pending_init(pending, response, addr,
+			      COAP_DEFAULT_MAX_RETRANSMIT);
 	if (r < 0) {
 		return -EINVAL;
 	}
diff --git a/subsys/net/lib/coap/coap.c b/subsys/net/lib/coap/coap.c
index 17767b2..ff0a1f7 100644
--- a/subsys/net/lib/coap/coap.c
+++ b/subsys/net/lib/coap/coap.c
@@ -1072,7 +1072,8 @@
 
 int coap_pending_init(struct coap_pending *pending,
 		      const struct coap_packet *request,
-		      const struct sockaddr *addr)
+		      const struct sockaddr *addr,
+		      uint8_t retries)
 {
 	memset(pending, 0, sizeof(*pending));
 
@@ -1083,6 +1084,7 @@
 	pending->data = request->data;
 	pending->len = request->offset;
 	pending->t0 = k_uptime_get_32();
+	pending->retries = retries;
 
 	return 0;
 }
@@ -1201,30 +1203,24 @@
  */
 #define INIT_ACK_TIMEOUT	CONFIG_COAP_INIT_ACK_TIMEOUT_MS
 
-static int32_t next_timeout(int32_t previous)
-{
-	switch (previous) {
-	case INIT_ACK_TIMEOUT:
-	case (INIT_ACK_TIMEOUT * 2):
-	case (INIT_ACK_TIMEOUT * 4):
-		return previous << 1;
-	case (INIT_ACK_TIMEOUT * 8):
-		/* equal value is returned to end retransmit */
-		return previous;
-	}
-
-	/* initial or unrecognized */
-	return INIT_ACK_TIMEOUT;
-}
-
 bool coap_pending_cycle(struct coap_pending *pending)
 {
-	int32_t old = pending->timeout;
+	if (pending->timeout == 0) {
+		/* Initial transmission. */
+		pending->timeout = INIT_ACK_TIMEOUT;
+
+		return true;
+	}
+
+	if (pending->retries == 0) {
+		return false;
+	}
 
 	pending->t0 += pending->timeout;
-	pending->timeout = next_timeout(pending->timeout);
+	pending->timeout = pending->timeout << 1;
+	pending->retries--;
 
-	return (old != pending->timeout);
+	return true;
 }
 
 void coap_pending_clear(struct coap_pending *pending)
diff --git a/subsys/net/lib/lwm2m/lwm2m_engine.c b/subsys/net/lib/lwm2m/lwm2m_engine.c
index 7a138a6..8c98fea 100644
--- a/subsys/net/lib/lwm2m/lwm2m_engine.c
+++ b/subsys/net/lib/lwm2m/lwm2m_engine.c
@@ -961,7 +961,8 @@
 		goto cleanup;
 	}
 
-	r = coap_pending_init(msg->pending, &msg->cpkt, &msg->ctx->remote_addr);
+	r = coap_pending_init(msg->pending, &msg->cpkt, &msg->ctx->remote_addr,
+			      COAP_DEFAULT_MAX_RETRANSMIT);
 	if (r < 0) {
 		LOG_ERR("Unable to initialize a pending "
 			"retransmission (err:%d).", r);
@@ -3996,7 +3997,9 @@
 		return -ENOMEM;
 	}
 
-	ret = coap_pending_init(msg->pending, &msg->cpkt, &msg->ctx->remote_addr);
+	ret = coap_pending_init(msg->pending, &msg->cpkt,
+				&msg->ctx->remote_addr,
+				COAP_DEFAULT_MAX_RETRANSMIT);
 	if (ret < 0) {
 		LOG_ERR("Unable to initialize a pending "
 			"retransmission (err:%d).", ret);
diff --git a/tests/net/lib/coap/src/main.c b/tests/net/lib/coap/src/main.c
index 0363ffe..902613b 100644
--- a/tests/net/lib/coap/src/main.c
+++ b/tests/net/lib/coap/src/main.c
@@ -1099,7 +1099,8 @@
 		goto done;
 	}
 
-	r = coap_pending_init(pending, &cpkt, (struct sockaddr *) &dummy_addr);
+	r = coap_pending_init(pending, &cpkt, (struct sockaddr *) &dummy_addr,
+			      COAP_DEFAULT_MAX_RETRANSMIT);
 	if (r < 0) {
 		TC_PRINT("Could not initialize packet\n");
 		goto done;