OpenThread Secure CoAP PSK DTLS handshake takes ~16 seconds on nRF54L15 DK

I am using OpenThread Secure CoAP with DTLS/PSK on an nRF54L15 DK.

Environment:

  • nRF Connect SDK 3.4.0
  • Zephyr 4.4.0
  • nRF54L15 DK
  • OpenThread Secure CoAP API (otCoapSecure*)
  • DTLS PSK authentication
  • Also tested with CONFIG_OPENTHREAD_NORDIC_LIBRARY_MASTER=y
  • Also reproduced with NCS 3.2.4
  • Tested as MTD/SED and FTD

The DTLS connection itself works correctly, but establishing the connection consistently takes about 16.2 seconds.

After the DTLS connection has been established, the actual CoAP request/response works normally.

A packet capture on the OpenThread Border Router shows that the server answers the DTLS packets within only a few milliseconds. However, the client waits approximately 8 seconds twice during the handshake.

Example timing:

Client -> Server
Server -> Client   ~27 ms later

~8 second delay

Client -> Server
Server -> Client

...

~8 second delay

Client -> Server

Handshake then continues normally

I inspected OpenThread's SecureTransport implementation and found:

mbedtls_ssl_conf_handshake_timeout(&mConf, 8000, 60000);

As a diagnostic test only, I changed the initial timeout from 8000 ms to 1000 ms:

mbedtls_ssl_conf_handshake_timeout(&mConf, 1000, 60000);

With this change, the DTLS connection time dropped reproducibly from approximately 16.2 seconds to approximately 2.2 seconds.

This seems to indicate that the handshake is waiting twice for the configured initial DTLS retransmission timer, even though the corresponding packets from the peer are received promptly.

The same behavior occurs:

  • with NCS 3.4.0
  • with NCS 3.2.4
  • with MTD/SED
  • with FTD
  • with the Nordic OpenThread master library

The server is a libcoap/mbedTLS CoAPS server using PSK authentication. Linux libcoap clients using mbedTLS, GnuTLS, and OpenSSL connect without this delay.

Could you please clarify:

  1. Is this a known issue in OpenThread Secure CoAP / SecureTransport?
  2. Is there a supported way to configure the DTLS initial retransmission timeout instead of modifying secure_transport.cpp?
  3. Could this be related to how SecureTransport::Process() handles received packets or MBEDTLS_ERR_SSL_HELLO_VERIFY_REQUIRED?
  4. Is there a recommended configuration for short-lived DTLS/CoAPS connections on battery-powered Thread devices

Here my source code:

#include <zephyr/kernel.h>

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include <zephyr/net/openthread.h>
#include <zephyr/logging/log.h>

#include <openthread/coap.h>
#include <openthread/coap_secure.h>
#include <openthread/ip6.h>
#include <openthread/message.h>
#include <openthread/netdata.h>
#include <openthread/thread.h>

LOG_MODULE_REGISTER(coaps_psk_client, LOG_LEVEL_INF);

#define COAP_PAYLOAD_SIZE 180

/*
 * Replace these values with the actual CoAPS server configuration.
 *
 * 192.0.2.1 belongs to TEST-NET-1 and is used here as a
 * documentation placeholder.
 */
#define COAPS_SERVER_IPV4_ADDRESS "192.0.2.1"
#define COAPS_SERVER_PORT 5684
#define COAPS_URI_PATH "telemetry"

#define COAPS_PSK_IDENTITY "test-device"
#define COAPS_PSK "0123456789abcdef0123456789abcdef"

#define COAP_TABLE "test_table"

#define SEND_INTERVAL_SECONDS 10
#define THREAD_WAIT_SECONDS 2
#define CONNECT_WAIT_SECONDS 30
#define RESPONSE_WAIT_SECONDS 10


typedef enum {
	STATE_WAIT_THREAD = 0,
	STATE_START_CONNECT,
	STATE_WAIT_CONNECT,
	STATE_SEND_REQUEST,
	STATE_WAIT_RESPONSE,
	STATE_DISCONNECT,
	STATE_SLEEP
} app_state_t;


static bool coaps_initialized;
static uint8_t server_ipv4[4];

static volatile app_state_t app_state = STATE_WAIT_THREAD;

static uint8_t connect_wait_counter;
static uint8_t response_wait_counter;
static int telemetry_counter;

static int64_t connect_start_ms;
static int64_t request_start_ms;


/* ---------------- Thread helper functions ---------------- */

static const char *state_to_string(app_state_t state)
{
	switch (state) {
	case STATE_WAIT_THREAD: return "WAIT_THREAD";
	case STATE_START_CONNECT: return "START_CONNECT";
	case STATE_WAIT_CONNECT: return "WAIT_CONNECT";
	case STATE_SEND_REQUEST: return "SEND_REQUEST";
	case STATE_WAIT_RESPONSE: return "WAIT_RESPONSE";
	case STATE_DISCONNECT: return "DISCONNECT";
	case STATE_SLEEP: return "SLEEP";
	default: return "UNKNOWN";
	}
}


static const char *role_to_string(otDeviceRole role)
{
	switch (role) {
	case OT_DEVICE_ROLE_DISABLED: return "disabled";
	case OT_DEVICE_ROLE_DETACHED: return "detached";
	case OT_DEVICE_ROLE_CHILD: return "child";
	case OT_DEVICE_ROLE_ROUTER: return "router";
	case OT_DEVICE_ROLE_LEADER: return "leader";
	default: return "unknown";
	}
}


static bool role_is_ready(otDeviceRole role)
{
	return role == OT_DEVICE_ROLE_CHILD ||
	       role == OT_DEVICE_ROLE_ROUTER ||
	       role == OT_DEVICE_ROLE_LEADER;
}


static otDeviceRole get_thread_role(otInstance *instance)
{
	otDeviceRole role;

	openthread_mutex_lock();
	role = otThreadGetDeviceRole(instance);
	openthread_mutex_unlock();

	return role;
}


/* ---------------- IPv4 / NAT64 ---------------- */

static bool parse_ipv4_address(const char *address, uint8_t ipv4[4])
{
	unsigned int octet[4];
	char trailing;

	if (sscanf(address, "%u.%u.%u.%u%c",
		   &octet[0], &octet[1], &octet[2], &octet[3], &trailing) != 4) {
		return false;
	}

	for (size_t i = 0; i < 4; i++) {
		if (octet[i] > 255) {
			return false;
		}

		ipv4[i] = (uint8_t)octet[i];
	}

	return true;
}


static bool initialize_server_ipv4(void)
{
	if (!parse_ipv4_address(COAPS_SERVER_IPV4_ADDRESS, server_ipv4)) {
		LOG_ERR("Invalid CoAPS server IPv4 address: %s",
			COAPS_SERVER_IPV4_ADDRESS);
		return false;
	}

	return true;
}


static bool find_nat64_prefix(otInstance *instance, otIp6Prefix *prefix)
{
	otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
	otExternalRouteConfig route;

	while (otNetDataGetNextRoute(instance, &iterator, &route) == OT_ERROR_NONE) {
		if (route.mNat64 && route.mPrefix.mLength == 96) {
			*prefix = route.mPrefix;
			return true;
		}
	}

	return false;
}


static void synthesize_nat64_address(const otIp6Prefix *prefix,
				     const uint8_t ipv4[4],
				     otIp6Address *address)
{
	memset(address, 0, sizeof(*address));

	memcpy(&address->mFields.m8[0],
	       &prefix->mPrefix.mFields.m8[0],
	       12);

	memcpy(&address->mFields.m8[12],
	       ipv4,
	       4);
}


/* ---------------- DTLS / CoAPS ---------------- */

static void coaps_connect_cb(otCoapSecureConnectEvent event, void *context)
{
	ARG_UNUSED(context);

	switch (event) {
	case OT_COAP_SECURE_CONNECTED:
		LOG_INF("CoAPS connected after %lld ms",
			(long long)(k_uptime_get() - connect_start_ms));

		app_state = STATE_SEND_REQUEST;
		break;

	case OT_COAP_SECURE_DISCONNECTED_ERROR:
		LOG_ERR("CoAPS disconnected: error");
		app_state = STATE_SLEEP;
		break;

	case OT_COAP_SECURE_DISCONNECTED_LOCAL_CLOSED:
		LOG_INF("CoAPS disconnected: local closed");
		break;

	case OT_COAP_SECURE_DISCONNECTED_MAX_ATTEMPTS:
		LOG_WRN("CoAPS disconnected: max attempts");
		app_state = STATE_SLEEP;
		break;

	case OT_COAP_SECURE_DISCONNECTED_PEER_CLOSED:
		LOG_WRN("CoAPS disconnected: peer closed");
		app_state = STATE_SLEEP;
		break;

	default:
		LOG_WRN("Unknown CoAPS connection event: %d", event);
		app_state = STATE_SLEEP;
		break;
	}
}


/*
 * Initialize OpenThread Secure CoAP with DTLS-PSK.
 */
static bool coaps_client_init(otInstance *instance)
{
	otError error;

	openthread_mutex_lock();

	otCoapSecureSetPsk(
		instance,
		(const uint8_t *)COAPS_PSK,
		(uint16_t)strlen(COAPS_PSK),
		(const uint8_t *)COAPS_PSK_IDENTITY,
		(uint16_t)strlen(COAPS_PSK_IDENTITY));

	otCoapSecureSetSslAuthMode(instance, false);

	error = otCoapSecureStart(instance, 0);

	openthread_mutex_unlock();

	if (error != OT_ERROR_NONE && error != OT_ERROR_ALREADY) {
		LOG_ERR("otCoapSecureStart failed: %d", error);
		return false;
	}

	LOG_INF("OpenThread CoAPS initialized with PSK");

	return true;
}


/*
 * Obtain the NAT64 destination address and start the DTLS handshake.
 */
static bool coaps_connect_start(otInstance *instance)
{
	otIp6Prefix nat64_prefix;
	otSockAddr peer_sock_addr;
	otError error;

	memset(&peer_sock_addr, 0, sizeof(peer_sock_addr));
	peer_sock_addr.mPort = COAPS_SERVER_PORT;

	openthread_mutex_lock();

	if (!find_nat64_prefix(instance, &nat64_prefix)) {
		openthread_mutex_unlock();
		LOG_WRN("No NAT64 /96 prefix available");
		return false;
	}

	synthesize_nat64_address(
		&nat64_prefix,
		server_ipv4,
		&peer_sock_addr.mAddress);

	connect_start_ms = k_uptime_get();

	error = otCoapSecureConnect(
		instance,
		&peer_sock_addr,
		coaps_connect_cb,
		NULL);

	openthread_mutex_unlock();

	if (error != OT_ERROR_NONE) {
		LOG_ERR("otCoapSecureConnect failed: %d", error);
		return false;
	}

	return true;
}


/* ---------------- CoAPS request ---------------- */

static void coaps_response_cb(void *context,
			      otMessage *message,
			      const otMessageInfo *message_info,
			      otError result)
{
	ARG_UNUSED(context);
	ARG_UNUSED(message_info);

	int64_t elapsed_ms = k_uptime_get() - request_start_ms;

	if (result == OT_ERROR_RESPONSE_TIMEOUT) {
		LOG_WRN("CoAPS response timeout after %lld ms",
			(long long)elapsed_ms);

		app_state = STATE_DISCONNECT;
		return;
	}

	if (result != OT_ERROR_NONE) {
		LOG_ERR("CoAPS response error %d after %lld ms",
			result,
			(long long)elapsed_ms);

		app_state = STATE_DISCONNECT;
		return;
	}

	if (message == NULL) {
		LOG_ERR("CoAPS response message is NULL");
		app_state = STATE_DISCONNECT;
		return;
	}

	otCoapCode code = otCoapMessageGetCode(message);

	LOG_INF("CoAPS response: code=%u.%02u, message_id=%u, time=%lld ms",
		(unsigned int)(code >> 5),
		(unsigned int)(code & 0x1f),
		(unsigned int)otCoapMessageGetMessageId(message),
		(long long)elapsed_ms);

	if (code == OT_COAP_CODE_CREATED) {
		LOG_INF("CoAPS request successful");
		telemetry_counter++;
	}

	app_state = STATE_DISCONNECT;
}


static bool send_secure_request(otInstance *instance)
{
	char payload[COAP_PAYLOAD_SIZE];
	otMessage *message;
	otError error;

	snprintf(payload,
		 sizeof(payload),
		 "{\"table\":\"%s\","
		 "\"device_id\":\"test-device\","
		 "\"temperature\":%d,"
		 "\"humidity\":%d}",
		 COAP_TABLE,
		 25 + (telemetry_counter % 10),
		 60 + (telemetry_counter % 5));

	openthread_mutex_lock();

	message = otCoapNewMessage(instance, NULL);

	if (message == NULL) {
		openthread_mutex_unlock();
		LOG_ERR("Failed to allocate CoAPS message");
		return false;
	}

	otCoapMessageInit(
		message,
		OT_COAP_TYPE_CONFIRMABLE,
		OT_COAP_CODE_POST);

	otCoapMessageGenerateToken(
		message,
		OT_COAP_DEFAULT_TOKEN_LENGTH);

	error = otCoapMessageAppendUriPathOptions(
		message,
		COAPS_URI_PATH);

	if (error != OT_ERROR_NONE) {
		LOG_ERR("Failed to append CoAPS URI path: %d", error);
		goto fail;
	}

	error = otCoapMessageAppendContentFormatOption(
		message,
		OT_COAP_OPTION_CONTENT_FORMAT_JSON);

	if (error != OT_ERROR_NONE) {
		LOG_ERR("Failed to append content format: %d", error);
		goto fail;
	}

	error = otCoapMessageSetPayloadMarker(message);

	if (error != OT_ERROR_NONE) {
		LOG_ERR("Failed to set payload marker: %d", error);
		goto fail;
	}

	error = otMessageAppend(
		message,
		payload,
		strlen(payload));

	if (error != OT_ERROR_NONE) {
		LOG_ERR("Failed to append payload: %d", error);
		goto fail;
	}

	request_start_ms = k_uptime_get();

	error = otCoapSecureSendRequest(
		instance,
		message,
		coaps_response_cb,
		NULL);

	if (error != OT_ERROR_NONE) {
		LOG_ERR("otCoapSecureSendRequest failed: %d", error);
		goto fail;
	}

	openthread_mutex_unlock();

	LOG_INF("CoAPS POST request sent");
	return true;

fail:
	otMessageFree(message);
	openthread_mutex_unlock();
	return false;
}


static void coaps_disconnect(otInstance *instance)
{
	openthread_mutex_lock();
	otCoapSecureDisconnect(instance);
	openthread_mutex_unlock();
}


/* ---------------- Main ---------------- */

int main(void)
{
	otInstance *instance;

	LOG_INF("OpenThread NAT64 CoAPS PSK test client started");

	instance = openthread_get_default_instance();

	if (instance == NULL) {
		LOG_ERR("No OpenThread instance");
		return 0;
	}

	if (!initialize_server_ipv4()) {
		return 0;
	}

	while (1) {
		LOG_DBG("State: %s", state_to_string(app_state));

		switch (app_state) {

		case STATE_WAIT_THREAD: {
			otDeviceRole role = get_thread_role(instance);

			if (!role_is_ready(role)) {
				LOG_INF("Thread role: %s - not ready",
					role_to_string(role));

				k_sleep(K_SECONDS(THREAD_WAIT_SECONDS));
				break;
			}

			LOG_INF("Thread role: %s - ready",
				role_to_string(role));

			if (!coaps_initialized) {
				if (!coaps_client_init(instance)) {
					k_sleep(K_SECONDS(THREAD_WAIT_SECONDS));
					break;
				}

				coaps_initialized = true;
			}

			app_state = STATE_START_CONNECT;
			break;
		}

		case STATE_START_CONNECT:
			connect_wait_counter = 0;

			/*
			 * Set WAIT_CONNECT before starting the asynchronous
			 * connection because the callback may be invoked
			 * immediately.
			 */
			app_state = STATE_WAIT_CONNECT;

			if (!coaps_connect_start(instance)) {
				app_state = STATE_SLEEP;
			}
			break;

		case STATE_WAIT_CONNECT:
			if (connect_wait_counter < CONNECT_WAIT_SECONDS) {
				connect_wait_counter++;
				k_sleep(K_SECONDS(1));
			} else {
				LOG_WRN("CoAPS connect timeout after %d seconds",
					CONNECT_WAIT_SECONDS);

				app_state = STATE_DISCONNECT;
			}
			break;

		case STATE_SEND_REQUEST:
			response_wait_counter = 0;

			app_state = STATE_WAIT_RESPONSE;

			if (!send_secure_request(instance)) {
				app_state = STATE_DISCONNECT;
			}
			break;

		case STATE_WAIT_RESPONSE:
			if (response_wait_counter < RESPONSE_WAIT_SECONDS) {
				response_wait_counter++;
				k_sleep(K_SECONDS(1));
			} else {
				LOG_WRN("CoAPS response timeout after %d seconds",
					RESPONSE_WAIT_SECONDS);

				app_state = STATE_DISCONNECT;
			}
			break;

		case STATE_DISCONNECT:
			coaps_disconnect(instance);
			app_state = STATE_SLEEP;
			break;

		case STATE_SLEEP:
			LOG_INF("Sleeping for %d seconds",
				SEND_INTERVAL_SECONDS);

			k_sleep(K_SECONDS(SEND_INTERVAL_SECONDS));
			app_state = STATE_WAIT_THREAD;
			break;

		default:
			LOG_ERR("Unknown application state");
			app_state = STATE_DISCONNECT;
			break;
		}
	}

	return 0;
}

  • Hello,

    This is not a known issue, as far as I am aware of at least. 

    Can you please try to enable the following log:

    # mbedtls debug output (routed into OT's log via SecureTransport's own callback)
    CONFIG_OPENTHREAD_MBEDTLS_DEBUG=y
    CONFIG_MBEDTLS_LOG_LEVEL_INF=y        # -> MBEDTLS_DEBUG_LEVEL=3
    
    # OpenThread logs, including the "SecTransport" module
    CONFIG_OPENTHREAD_LOG_LEVEL_DEBG=y
    
    CONFIG_OPENTHREAD_SHELL=y
    CONFIG_LOG=y
    CONFIG_LOG_MODE_DEFERRED=y
    CONFIG_LOG_BUFFER_SIZE=16384
    

    Does this produce any useful logs? Can you send the logs with these settings enabled?

    Can you also capture a sniffer trace of the traffic when it takes 16 seconds? It does sound like the client -> server packet is fine, so the question is what is happening to the packet from the server to the client. What it looks like on air.

    Can you also try to capture the following directly before and directly after the 16 seconds transaction:

    ot bufferinfo
    ot counters mac
    ot ipaddr
    ot netdata show
    ot pollperiod
    ot mode
    ot parent
    

    Is there a way for me to reproduce what you are seeing? Are you able to reproduce the issue you are seeing using just 2 (or 3) DKs?

    Best regards,

    Edvin

  • Hi Edvin,

    I did some additional tests with the OpenThread/mbedTLS debug logging and an external nRF52840 DK running the Nordic IEEE 802.15.4 sniffer.

    The main result is that the ~8 second intervals are reproducible and correlate very closely with the DTLS retransmission timer.

    More importantly, I captured the same transaction at several points:
    * on the CoAPS server
    * on the Border Router Ethernet interface
    * on the Border Router wpan0 interface
    * over the air using the external IEEE 802.15.4 sniffer
    * on the client using OpenThread/mbedTLS debug logging

    The CoAPS server itself responds immediately. For example, for the initial ClientHello:

    Server capture:
    ClientHello received:     t = 0.000000
    HelloVerifyRequest sent:  t = 0.001664

    On the Border Router Ethernet interface, the response is already back after about 28 ms.

    However, in one SED test the external IEEE 802.15.4 sniffer did not see the HelloVerifyRequest over the air until approximately 8 seconds after the ClientHello.

    A similar pattern occurred at the next DTLS stage: the server generated ServerHello/ServerHelloDone immediately and retransmitted the flight after approximately 1 s, 2 s and 4 s, while the client only made progress after another ~8 s cycle.

    I also repeated the test using an FTD instead of an SED. The delay is shorter and very reproducible, but still present. With the Raspberry Pi/OpenThread Border Router the DTLS handshake took about 16.5 seconds.

    To exclude the Raspberry Pi Border Router implementation as the cause, I repeated the same FTD test with an M5Stack Border Router. The result was essentially the same:

    CoAPS connection started: 1.094 s
    CoAPS connected:          17.514 s
    DTLS handshake duration:  16.422 s
    Complete test finished:   21.814 s

    So the behavior is reproducible with two different Border Router implementations.

    This suggests that the Raspberry Pi Border Router itself is probably not the root cause. SED polling also cannot fully explain the issue because the same ~8 second retransmission pattern remains with an FTD.

    I attached a ZIP file containing the logs and packet captures from the SED test:

    * coaps_connect.log – client OpenThread/mbedTLS debug log
    * sniffing.pcap – external IEEE 802.15.4 sniffer capture
    * dtls_wpan0_anon.pcap – Border Router wpan0 capture
    * dtls_eth0_anon.pcap – Border Router Ethernet capture
    * dtls_server_anon.pcap – server-side DTLS capture

    For privacy, I replaced the public IPv4 address of the CoAPS server with a documentation address in the anonymized PCAP files. The packet timing and protocol contents are otherwise unchanged.

    Best regards,
    Markus

    log_test.zip

  • I have done some additional tests and I think I have now narrowed the issue down to the polling behavior of sleepy devices.

    One important finding was that my previous FTD test was misleading. Although the application was built as an FTD, the device was still operating with RxOnWhenIdle = false and was therefore using MAC Data Requests to poll its parent. This was visible in the 802.15.4 sniffer trace.

    When I explicitly configured the device with:

    otLinkModeConfig link_mode = {
        .mRxOnWhenIdle = true,
        .mDeviceType = true,
        .mNetworkData = true,
    };
    
    otThreadSetLinkMode(instance, link_mode);
    

    the DTLS handshake time dropped from about 16 seconds to approximately 185 ms.

    I then repeated the test with a real sleepy MTD, without changing its Thread link mode.

    The normal settings reported by OpenThread were:

    RxOnWhenIdle=0
    DeviceType=0
    NetworkData=1
    Current poll period: 236000 ms
    

    With this configuration, the DTLS handshake again took about 16 seconds.

    As another test, I only changed the poll period temporarily before calling otCoapSecureConnect():

    uint32_t old_poll_period = otLinkGetPollPeriod(instance);
    
    otLinkSetPollPeriod(instance, 100);
    
    /* DTLS connect + CoAPS request/response */
    
    otLinkSetPollPeriod(instance, old_poll_period);
    

    The device remained a sleepy MTD (RxOnWhenIdle=0), but the result changed dramatically:

    Normal SED polling:
    DTLS connection: ~16.2 s
    CoAPS response:  ~2.9 s
    
    Temporary 100 ms polling:
    DTLS connection: 327 ms
    CoAPS response:  112 ms
    
    RxOnWhenIdle=true:
    DTLS connection: ~185 ms
    

    So the long delay appears to be caused by the sleepy-device polling behavior during the DTLS handshake rather than by the server, NAT64, Border Router, or DTLS processing itself.

    The 8-second intervals seen previously also correlate with the initial mbedTLS DTLS retransmission timeout. It looks as if an expected DTLS response is not retrieved promptly by the sleepy child, and the next DTLS retransmission eventually causes further communication/polling.

    My question is therefore whether this is the expected behavior of otCoapSecureConnect() on a sleepy end device.

    Since the DTLS handshake consists of several request/response flights where an immediate response from the peer is expected, I would have expected OpenThread Secure CoAP to temporarily use faster data polling while the handshake is in progress.

    Is an application expected to manage the SED poll period itself around otCoapSecureConnect(), or should Secure CoAP/OpenThread normally trigger fast polling automatically in this situation?

    If the latter is expected, this might indicate a missing interaction between Secure CoAP/DTLS and the OpenThread data polling mechanism for sleepy devices.

  • Hello,

    I didn't realize we were talking about a sleepy device, but that explains it. When a sleepy device is idling, it's radio will be turned off. 

    As far as I am aware, there is no automatic way that the polling time is adjusted when handling sleepy devices. However, if you are working on e.g. a light switch, then it may make sense to set the polling period to a shorter period when the device is being set up, and perhaps a minute or so after that, before returning to a heavier sleep state. 

    Alternatively, you can push the button on that switch to wake it up exactly when you expect to send a message to it. 

    Best regards,

    Edvin

  • Hello Edvin,

    thanks for the clarification.

    One point I would like to clarify is that the behavior I observed does not seem to be limited to devices explicitly configured as Sleepy End Devices.

    In my tests, the relevant condition was RxOnWhenIdle = false. I observed the same delayed DTLS behavior with an MTD and also with an FTD-capable device while RxOnWhenIdle was disabled. Once I explicitly enabled RxOnWhenIdle, the DTLS handshake time dropped from roughly 16 seconds to about 185 ms.

    On the actual sleepy MTD, the normal poll period was 236000 ms. Temporarily reducing the poll period to 100 ms during the DTLS/CoAP exchange reduced the handshake to about 330 ms and the CoAP response time to around 120 ms, while still keeping the device in Rx-off-when-idle mode.

    So from my understanding, this seems to be more generally related to Rx-off-when-idle children rather than specifically to Sleepy End Devices.

    What I am still wondering about is whether Secure CoAP/DTLS could or should trigger temporary fast polling while a handshake or a confirmable CoAP exchange is in progress. At that point the stack knows that responses are expected, and OpenThread already uses different polling behavior in some other situations.

    I am also not entirely sure whether this is mainly an NCS integration question or rather an OpenThread behavior/design question. Since Secure CoAP and the polling logic are part of OpenThread, I suspect it may be more of an upstream OpenThread topic, but I would appreciate your view on that.

    Best regards,
    Markus

Related