[thread-direct] add KeyManager and key management APIs (#13770)

This commit introduces the Thread Direct (TD) key management module
(`ThreadDirect::KeyManager`, `Key`, and `KeyId`) along with its public
C APIs in `<openthread/thread_direct.h>`.

- Add `KeyManager` and `otThreadDirect*Key*` APIs to register, find,
  iterate over, and unregister Thread Direct keys, supporting both
  literal keys and `otCryptoKeyRef` key references.
- Track the MAC security frame counter per `Key` and persist an ahead
  frame counter in `Settings`. When a key's frame counter is
  incremented and reaches the saved counter, `KeyManager` posts a
  tasklet to re-save the key with a newly advanced counter
  (adding the updated entry before removing stale entries from
  `Settings` to ensure crash safety across resets).
- Restore saved keys on instance initialization and clear them on
  factory reset or persistent info erase.
- Add Thread Direct compile-time configs in `config/thread_direct.h`
  for configuring pool vs. heap key allocation, max registered keys,
  and persistent frame counter ahead value.
- Add `test_thread_direct_key_manager` Nexus test covering key
  registration, lookup, iteration, capacity limits, frame counter
  advancement and persistence across resets, and unregistration.
This commit is contained in:
Abtin Keshavarzian
2026-10-27 22:45:19 +01:00
committed by GitHub
parent 1f6c3ad433
commit 7009584dad
26 changed files with 2001 additions and 37 deletions
+1
View File
@@ -131,6 +131,7 @@
* @defgroup api-seeker Seeker
* @defgroup api-server Server
* @defgroup api-steering-data Steering Data
* @defgroup api-thread-direct Thread Direct
*
* @}
*
+1
View File
@@ -132,6 +132,7 @@ source_set("openthread") {
"tcp.h",
"tcp_ext.h",
"thread.h",
"thread_direct.h",
"thread_ftd.h",
"trel.h",
"udp.h",
+1 -1
View File
@@ -51,7 +51,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (632)
#define OPENTHREAD_API_VERSION (633)
/**
* @addtogroup api-instance
+16 -14
View File
@@ -62,20 +62,22 @@ extern "C" {
*/
enum
{
OT_SETTINGS_KEY_ACTIVE_DATASET = 0x0001, ///< Active Operational Dataset.
OT_SETTINGS_KEY_PENDING_DATASET = 0x0002, ///< Pending Operational Dataset.
OT_SETTINGS_KEY_NETWORK_INFO = 0x0003, ///< Thread network information.
OT_SETTINGS_KEY_PARENT_INFO = 0x0004, ///< Parent information.
OT_SETTINGS_KEY_CHILD_INFO = 0x0005, ///< Child information.
OT_SETTINGS_KEY_SLAAC_IID_SECRET_KEY = 0x0007, ///< SLAAC key to generate semantically opaque IID.
OT_SETTINGS_KEY_DAD_INFO = 0x0008, ///< Duplicate Address Detection (DAD) information.
OT_SETTINGS_KEY_SRP_ECDSA_KEY = 0x000b, ///< SRP client ECDSA public/private key pair.
OT_SETTINGS_KEY_SRP_CLIENT_INFO = 0x000c, ///< The SRP client info (selected SRP server address).
OT_SETTINGS_KEY_SRP_SERVER_INFO = 0x000d, ///< The SRP server info (UDP port).
OT_SETTINGS_KEY_BR_ULA_PREFIX = 0x000f, ///< BR ULA prefix.
OT_SETTINGS_KEY_BR_ON_LINK_PREFIXES = 0x0010, ///< BR local on-link prefixes.
OT_SETTINGS_KEY_BORDER_AGENT_ID = 0x0011, ///< Unique Border Agent/Router ID.
OT_SETTINGS_KEY_TCAT_COMMR_CERT = 0x0012, ///< TCAT Commissioner certificate
OT_SETTINGS_KEY_ACTIVE_DATASET = 0x0001, ///< Active Operational Dataset.
OT_SETTINGS_KEY_PENDING_DATASET = 0x0002, ///< Pending Operational Dataset.
OT_SETTINGS_KEY_NETWORK_INFO = 0x0003, ///< Thread network information.
OT_SETTINGS_KEY_PARENT_INFO = 0x0004, ///< Parent information.
OT_SETTINGS_KEY_CHILD_INFO = 0x0005, ///< Child information.
OT_SETTINGS_KEY_SLAAC_IID_SECRET_KEY = 0x0007, ///< SLAAC key to generate semantically opaque IID.
OT_SETTINGS_KEY_DAD_INFO = 0x0008, ///< Duplicate Address Detection (DAD) information.
OT_SETTINGS_KEY_SRP_ECDSA_KEY = 0x000b, ///< SRP client ECDSA public/private key pair.
OT_SETTINGS_KEY_SRP_CLIENT_INFO = 0x000c, ///< The SRP client info (selected SRP server address).
OT_SETTINGS_KEY_SRP_SERVER_INFO = 0x000d, ///< The SRP server info (UDP port).
OT_SETTINGS_KEY_BR_ULA_PREFIX = 0x000f, ///< BR ULA prefix.
OT_SETTINGS_KEY_BR_ON_LINK_PREFIXES = 0x0010, ///< BR local on-link prefixes.
OT_SETTINGS_KEY_BORDER_AGENT_ID = 0x0011, ///< Unique Border Agent/Router ID.
OT_SETTINGS_KEY_TCAT_COMMR_CERT = 0x0012, ///< TCAT Commissioner certificate.
OT_SETTINGS_KEY_THREAD_DIRECT_KEYS = 0x0013, ///< Thread Direct Keys using literal key format.
OT_SETTINGS_KEY_THREAD_DIRECT_KEY_REFS = 0x0014, ///< Thread Direct Keys using key references.
// Deprecated and reserved key values:
//
+236
View File
@@ -0,0 +1,236 @@
/*
* Copyright (c) 2026, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* @brief
* This file defines the OpenThread Thread Direct APIs.
*/
#ifndef OPENTHREAD_THREAD_DIRECT_H_
#define OPENTHREAD_THREAD_DIRECT_H_
#include <stdint.h>
#include <openthread/instance.h>
#include <openthread/platform/radio.h>
#include <openthread/platform/toolchain.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @addtogroup api-thread-direct
*
* @brief
* This module includes functions and definitions for Thread Direct (TD).
*
* The APIs in this module require Thread Direct functionality to be enabled (e.g., via
* `OPENTHREAD_CONFIG_TD_WAKE_INITIATOR_ENABLE` or `OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE`).
*
* @{
*/
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Thread Direct Key
#define OT_THREAD_DIRECT_KEY_SIZE OT_MAC_KEY_SIZE ///< Size of a Thread Direct Key in bytes (16 bytes).
#define OT_THREAD_DIRECT_KEY_ID_SIZE 5 ///< Size of a Thread Direct Key ID in bytes.
/**
* An opaque type representing a registered Thread Direct key entry in OpenThread.
*/
typedef struct otThreadDirectKey otThreadDirectKey;
/**
* Represents Thread Direct key material (literal key bytes or cryptographic key reference).
*
* Depending on the build-time configuration `OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE`, either
* `mKeyMaterial.mKey` or `mKeyMaterial.mKeyRef` is used:
*
* - When `OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE` is disabled, `mKeyMaterial.mKey.m8` is used to specify or
* retrieve the literal 16-byte key material.
* - When `OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE` is enabled, `mKeyMaterial.mKeyRef` is used to pass or
* retrieve the `otMacKeyRef` (`otCryptoKeyRef`) associated with the key in platform crypto storage.
*
* When key references (`mKeyMaterial.mKeyRef`) are used, the next higher layer is responsible for importing/creating
* the key in platform crypto storage prior to registering it with OpenThread. The key reference MUST be stored
* persistently in platform crypto storage so that the registered `mKeyRef` remains valid across device reboots/resets.
* The caller MAY reuse the same `mKeyRef` associated with an already registered Thread Direct key when registering
* additional keys with different Key IDs (`otThreadDirectKeyId`). Upon successful registration via
* `otThreadDirectRegisterKey()`, OpenThread manages the lifetime of the `mKeyRef` and will destroy/remove the key from
* platform crypto storage (via `otPlatCryptoDestroyKey()`) once all registered Thread Direct keys using that `mKeyRef`
* have been unregistered.
*/
typedef otMacKeyMaterial otThreadDirectKeyMaterial;
/**
* @struct otThreadDirectKeyId
*
* Represents a Thread Direct Key ID.
*/
OT_TOOL_PACKED_BEGIN
struct otThreadDirectKeyId
{
uint8_t m8[OT_THREAD_DIRECT_KEY_ID_SIZE]; ///< Key ID bytes.
} OT_TOOL_PACKED_END;
typedef struct otThreadDirectKeyId otThreadDirectKeyId;
/**
* Represents an iterator for iterating through registered Thread Direct keys.
*
* Use `otThreadDirectInitKeyIterator()` to initialize the iterator before using it with `otThreadDirectGetNextKey()`.
*
* The caller MUST NOT access or update the fields in this struct. It is intended for OpenThread internal use only.
*/
typedef struct otThreadDirectKeyIterator
{
const void *mPtr;
} otThreadDirectKeyIterator;
/**
* Registers a Thread Direct key and its associated Key ID with OpenThread.
*
* If a matching key with the same @p aKeyMaterial and @p aKeyId is already registered, this function returns a pointer
* to the existing `otThreadDirectKey` entry. Otherwise, it allocates and registers a new entry. If a new entry cannot
* be allocated (e.g., the maximum number of allowed/supported registered keys is reached), `NULL` is returned.
*
* As described in `otThreadDirectKeyMaterial`, either `aKeyMaterial->mKeyMaterial.mKey` or
* `aKeyMaterial->mKeyMaterial.mKeyRef` is used depending on whether `OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE`
* is enabled. When using `mKeyMaterial.mKeyRef`, the caller MUST ensure that the key reference is stored persistently
* in platform crypto storage across reboots, and MAY reuse the same `mKeyRef` across multiple registered keys with
* different Key IDs. Upon successful registration, OpenThread manages `aKeyMaterial->mKeyMaterial.mKeyRef` and will
* destroy/remove the key from platform crypto storage once all Thread Direct keys using that `mKeyRef` are
* unregistered.
*
* @param[in] aInstance The OpenThread instance.
* @param[in] aKeyMaterial The Thread Direct key material to register.
* @param[in] aKeyId The Thread Direct Key ID to register.
*
* @returns A pointer to the registered `otThreadDirectKey` entry, or `NULL` if it could not be registered.
*/
const otThreadDirectKey *otThreadDirectRegisterKey(otInstance *aInstance,
const otThreadDirectKeyMaterial *aKeyMaterial,
const otThreadDirectKeyId *aKeyId);
/**
* Finds a registered Thread Direct key matching the given key material and Key ID.
*
* @param[in] aInstance The OpenThread instance.
* @param[in] aKeyMaterial The key material to match.
* @param[in] aKeyId The Key ID to match.
*
* @returns A pointer to the matching registered `otThreadDirectKey`, or `NULL` if no match was found.
*/
const otThreadDirectKey *otThreadDirectFindKey(otInstance *aInstance,
const otThreadDirectKeyMaterial *aKeyMaterial,
const otThreadDirectKeyId *aKeyId);
/**
* Unregisters a previously registered Thread Direct key from OpenThread.
*
* Once unregistered, the @p aKey pointer is no longer valid and MUST NOT be used.
*
* When `OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE` is enabled, this call also destroys/removes the associated
* key reference (`mKeyRef`) from platform crypto storage (via `otPlatCryptoDestroyKey()`) if no other registered
* Thread Direct key is still using the same `mKeyRef`.
*
* @param[in] aInstance The OpenThread instance.
* @param[in] aKey The registered `otThreadDirectKey` to unregister.
*/
void otThreadDirectUnregisterKey(otInstance *aInstance, const otThreadDirectKey *aKey);
/**
* Unregisters all previously registered Thread Direct keys from OpenThread.
*
* When `OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE` is enabled, this call also destroys/removes the associated
* key references (`mKeyRef`) for all registered keys from platform crypto storage (via `otPlatCryptoDestroyKey()`).
*
* @param[in] aInstance The OpenThread instance.
*/
void otThreadDirectUnregisterAllKeys(otInstance *aInstance);
/**
* Initializes an `otThreadDirectKeyIterator`.
*
* An iterator MUST be initialized before it is used with `otThreadDirectGetNextKey()`. An iterator can be initialized
* again to restart iteration from the beginning of the registered keys list.
*
* @param[in] aInstance The OpenThread instance.
* @param[out] aIterator The iterator to initialize.
*/
void otThreadDirectInitKeyIterator(otInstance *aInstance, otThreadDirectKeyIterator *aIterator);
/**
* Iterates through the registered Thread Direct keys and returns the next key entry.
*
* @param[in,out] aIterator The iterator. MUST be initialized via `otThreadDirectInitKeyIterator()`.
*
* @returns A pointer to the next registered `otThreadDirectKey`, or `NULL` if the end of the list is reached.
*/
const otThreadDirectKey *otThreadDirectGetNextKey(otThreadDirectKeyIterator *aIterator);
/**
* Gets the key material associated with a registered Thread Direct key.
*
* @param[in] aKey A registered `otThreadDirectKey`.
*
* @returns The `otThreadDirectKeyMaterial` associated with @p aKey.
*/
const otThreadDirectKeyMaterial *otThreadDirectKeyGetMaterial(const otThreadDirectKey *aKey);
/**
* Gets the Key ID associated with a registered Thread Direct key.
*
* @param[in] aKey A registered `otThreadDirectKey`.
*
* @returns The `otThreadDirectKeyId` associated with @p aKey.
*/
const otThreadDirectKeyId *otThreadDirectKeyGetId(const otThreadDirectKey *aKey);
/**
* Gets the current MAC security frame counter associated with a registered Thread Direct key.
*
* @param[in] aKey A registered `otThreadDirectKey`.
*
* @returns The MAC security frame counter associated with @p aKey.
*/
uint32_t otThreadDirectKeyGetFrameCounter(const otThreadDirectKey *aKey);
/**
* @}
*/
#ifdef __cplusplus
} // extern "C"
#endif
#endif // OPENTHREAD_THREAD_DIRECT_H_
+5
View File
@@ -377,6 +377,7 @@ openthread_core_files = [
"api/tcp_api.cpp",
"api/tcp_ext_api.cpp",
"api/thread_api.cpp",
"api/thread_direct_api.cpp",
"api/thread_ftd_api.cpp",
"api/trel_api.cpp",
"api/udp_api.cpp",
@@ -781,6 +782,10 @@ openthread_core_files = [
"thread/vendor_info.hpp",
"thread/version.cpp",
"thread/version.hpp",
"thread_direct/td_key_manager.cpp",
"thread_direct/td_key_manager.hpp",
"thread_direct/td_types.cpp",
"thread_direct/td_types.hpp",
"utils/channel_manager.cpp",
"utils/channel_manager.hpp",
"utils/channel_monitor.cpp",
+3
View File
@@ -91,6 +91,7 @@ set(COMMON_SOURCES
api/tcp_api.cpp
api/tcp_ext_api.cpp
api/thread_api.cpp
api/thread_direct_api.cpp
api/thread_ftd_api.cpp
api/trel_api.cpp
api/udp_api.cpp
@@ -282,6 +283,8 @@ set(COMMON_SOURCES
thread/uri_paths.cpp
thread/vendor_info.cpp
thread/version.cpp
thread_direct/td_key_manager.cpp
thread_direct/td_types.cpp
utils/channel_manager.cpp
utils/channel_monitor.cpp
utils/flash.cpp
+90
View File
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2026, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements the Thread Direct APIs.
*/
#include "openthread-core-config.h"
#if OT_CONFIG_THREAD_DIRECT_ENABLE
#include <openthread/thread_direct.h>
#include "common/as_core_type.hpp"
#include "common/const_cast.hpp"
#include "instance/instance.hpp"
using namespace ot;
const otThreadDirectKey *otThreadDirectRegisterKey(otInstance *aInstance,
const otThreadDirectKeyMaterial *aKeyMaterial,
const otThreadDirectKeyId *aKeyId)
{
return AsCoreType(aInstance).Get<ThreadDirect::KeyManager>().RegisterKey(AsCoreType(aKeyMaterial),
AsCoreType(aKeyId));
}
const otThreadDirectKey *otThreadDirectFindKey(otInstance *aInstance,
const otThreadDirectKeyMaterial *aKeyMaterial,
const otThreadDirectKeyId *aKeyId)
{
return AsCoreType(aInstance).Get<ThreadDirect::KeyManager>().FindKey(AsCoreType(aKeyMaterial), AsCoreType(aKeyId));
}
void otThreadDirectUnregisterKey(otInstance *aInstance, const otThreadDirectKey *aKey)
{
AsCoreType(aInstance).Get<ThreadDirect::KeyManager>().UnregisterKey(AsNonConst(AsCoreType(aKey)));
}
void otThreadDirectUnregisterAllKeys(otInstance *aInstance)
{
AsCoreType(aInstance).Get<ThreadDirect::KeyManager>().UnregisterAllKeys();
}
void otThreadDirectInitKeyIterator(otInstance *aInstance, otThreadDirectKeyIterator *aIterator)
{
AsCoreType(aIterator).Init(AsCoreType(aInstance));
}
const otThreadDirectKey *otThreadDirectGetNextKey(otThreadDirectKeyIterator *aIterator)
{
return AsCoreType(aIterator).GetNext();
}
const otThreadDirectKeyMaterial *otThreadDirectKeyGetMaterial(const otThreadDirectKey *aKey)
{
return &AsCoreType(aKey).GetKeyMaterial();
}
const otThreadDirectKeyId *otThreadDirectKeyGetId(const otThreadDirectKey *aKey) { return &AsCoreType(aKey).GetId(); }
uint32_t otThreadDirectKeyGetFrameCounter(const otThreadDirectKey *aKey) { return AsCoreType(aKey).GetFrameCounter(); }
#endif // OT_CONFIG_THREAD_DIRECT_ENABLE
+2
View File
@@ -95,6 +95,8 @@ protected:
*/
template <class Type, uint16_t kPoolSize> class Pool : public PoolBase<Type>
{
static_assert(kPoolSize > 0, "kPoolSize cannot be zero!");
public:
/**
* Initializes the pool.
+133 -4
View File
@@ -42,6 +42,28 @@ RegisterLogModule("Settings");
//---------------------------------------------------------------------------------------------------------------------
// SettingsBase
#if OT_CONFIG_THREAD_DIRECT_ENABLE
bool SettingsBase::ThreadDirectKeyInfo::Matches(const ThreadDirect::Key &aKey) const
{
bool matches = false;
#if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
VerifyOrExit(GetKey() == aKey.GetKeyMaterial().GetKey());
#else
VerifyOrExit(GetKeyRef() == aKey.GetKeyMaterial().GetKeyRef());
#endif
VerifyOrExit(aKey.HasId(GetKeyId()));
matches = true;
exit:
return matches;
}
#endif
// LCOV_EXCL_START
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
@@ -105,6 +127,14 @@ void SettingsBase::BorderAgentId::Log(Action aAction, const MeshCoP::BorderAgent
}
#endif
#if OT_CONFIG_THREAD_DIRECT_ENABLE
void SettingsBase::ThreadDirectKeyInfo::Log(Action aAction) const
{
LogInfo("%s ThreadDirectKeyInfo {id:%s, cntr:0x%lx}", ActionToString(aAction), GetKeyId().ToString().AsCString(),
ToUlong(GetFrameCounter()));
}
#endif
#endif // OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
@@ -117,7 +147,7 @@ const char *SettingsBase::ActionToString(Action aAction)
_(kActionDelete, "Deleted") \
FtdActionMapList(_)
#if OPENTHREAD_FTD
#if OPENTHREAD_FTD || OT_CONFIG_THREAD_DIRECT_ENABLE
#define FtdActionMapList(_) \
_(kActionAdd, "Added") \
_(kActionRemove, "Removed") \
@@ -154,11 +184,13 @@ const char *SettingsBase::KeyToString(Key aKey)
_(kKeyBrUlaPrefix, "BrUlaPrefix") \
_(kKeyBrOnLinkPrefixes, "BrOnLinkPrefixes") \
_(kKeyBorderAgentId, "BorderAgentId") \
_(kKeyTcatCommrCert, "TcatCommrCert")
_(kKeyTcatCommrCert, "TcatCommrCert") \
_(kKeyThreadDirectKeys, "ThreadDirectKeys") \
_(kKeyThreadDirectKeyRefs, "ThreadDirectKeyRefs")
DefineEnumStringArray(KeyMapList);
static_assert(kLastKey == kKeyTcatCommrCert, "kLastKey is not valid");
static_assert(kLastKey == kKeyThreadDirectKeyRefs, "kLastKey is not valid");
OT_ASSERT(aKey <= kLastKey);
@@ -176,6 +208,7 @@ const uint16_t Settings::kSensitiveKeys[] = {
SettingsBase::kKeyActiveDataset,
SettingsBase::kKeyPendingDataset,
SettingsBase::kKeySrpEcdsaKey,
SettingsBase::kKeyThreadDirectKeys,
};
void Settings::Init(void) { Get<SettingsDriver>().Init(kSensitiveKeys, GetArrayLength(kSensitiveKeys)); }
@@ -372,6 +405,102 @@ void Settings::BrOnLinkPrefix::Log(const char *aActionText) const
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
#if OT_CONFIG_THREAD_DIRECT_ENABLE
void Settings::SaveThreadDirectKey(ThreadDirect::Key &aKey)
{
ThreadDirectKeyInfo keyInfo;
keyInfo.Init();
#if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
keyInfo.SetKey(aKey.GetKeyMaterial().GetKey());
#else
keyInfo.SetKeyRef(aKey.GetKeyMaterial().GetKeyRef());
#endif
keyInfo.SetKeyId(aKey.GetId());
keyInfo.SetFrameCounter(aKey.UpdateSavedFrameCounter());
// Add the new entry with the updated frame counter first before
// removing any previously saved entries for the same key. This
// ensures that the key entry is never lost if a power loss or
// reset occurs mid-operation.
SuccessOrAssert(Get<SettingsDriver>().Add(ThreadDirectKeyInfo::kKey, &keyInfo, sizeof(ThreadDirectKeyInfo)));
keyInfo.Log(kActionSave);
RemoveMatchingThreadDirectKeys(aKey, /* aKeepEntryMatchingFrameCounter */ true);
}
void Settings::DeleteThreadDirectKey(const ThreadDirect::Key &aKey)
{
RemoveMatchingThreadDirectKeys(aKey, /* aKeepEntryMatchingFrameCounter */ false);
}
void Settings::RemoveMatchingThreadDirectKeys(const ThreadDirect::Key &aKey, bool aKeepEntryMatchingFrameCounter)
{
// Removes all saved `ThreadDirectKeyInfo` entries matching
// `aKey`. When `aKeepEntryMatchingFrameCounter` is true (used
// after saving an updated entry in `SaveThreadDirectKey()`), any
// entry whose frame counter matches `aKey.GetSavedFrameCounter()`
// is kept and only older entries with stale frame counters are
// removed.
ThreadDirectKeyInfo keyInfo;
while (true)
{
for (int index = 0; true; index++)
{
SuccessOrExit(ReadThreadDirectKey(index, keyInfo));
if (!keyInfo.Matches(aKey))
{
continue;
}
if (aKeepEntryMatchingFrameCounter && (keyInfo.GetFrameCounter() == aKey.GetSavedFrameCounter()))
{
continue;
}
IgnoreError(Get<SettingsDriver>().Delete(ThreadDirectKeyInfo::kKey, index));
if (!aKeepEntryMatchingFrameCounter)
{
keyInfo.Log(kActionDelete);
}
// Deleting an entry from the settings list can shift or
// reorder the remaining entries' indices, so we restart
// the inner scan from index 0 after each deletion.
break;
}
}
exit:
return;
}
void Settings::DeleteAllThreadDirectKeys(void)
{
Error error = Get<SettingsDriver>().Delete(ThreadDirectKeyInfo::kKey);
Log(kActionDeleteAll, error, ThreadDirectKeyInfo::kKey);
}
Error Settings::ReadThreadDirectKey(int aIndex, ThreadDirectKeyInfo &aKeyInfo) const
{
uint16_t length = sizeof(aKeyInfo);
aKeyInfo.Init();
return Get<SettingsDriver>().Get(ThreadDirectKeyInfo::kKey, aIndex, &aKeyInfo, &length);
}
#endif // OT_CONFIG_THREAD_DIRECT_ENABLE
Error Settings::ReadEntry(Key aKey, void *aValue, uint16_t aMaxLength) const
{
Error error;
@@ -437,7 +566,7 @@ void Settings::Log(Action aAction, Error aError, Key aKey, const void *aValue)
actionText = "deleting";
break;
#if OPENTHREAD_FTD
#if OPENTHREAD_FTD || OT_CONFIG_THREAD_DIRECT_ENABLE
case kActionAdd:
actionText = "adding";
break;
+162 -15
View File
@@ -52,6 +52,7 @@
#include "net/ip6_address.hpp"
#include "net/slaac_address.hpp"
#include "thread/version.hpp"
#include "thread_direct/td_types.hpp"
#include "utils/flash.hpp"
namespace ot {
@@ -72,7 +73,7 @@ protected:
kActionSave,
kActionResave,
kActionDelete,
#if OPENTHREAD_FTD
#if OPENTHREAD_FTD || OT_CONFIG_THREAD_DIRECT_ENABLE
kActionAdd,
kActionRemove,
kActionDeleteAll,
@@ -106,22 +107,24 @@ public:
*/
enum Key : uint16_t
{
kKeyActiveDataset = OT_SETTINGS_KEY_ACTIVE_DATASET,
kKeyPendingDataset = OT_SETTINGS_KEY_PENDING_DATASET,
kKeyNetworkInfo = OT_SETTINGS_KEY_NETWORK_INFO,
kKeyParentInfo = OT_SETTINGS_KEY_PARENT_INFO,
kKeyChildInfo = OT_SETTINGS_KEY_CHILD_INFO,
kKeySlaacIidSecretKey = OT_SETTINGS_KEY_SLAAC_IID_SECRET_KEY,
kKeySrpEcdsaKey = OT_SETTINGS_KEY_SRP_ECDSA_KEY,
kKeySrpClientInfo = OT_SETTINGS_KEY_SRP_CLIENT_INFO,
kKeySrpServerInfo = OT_SETTINGS_KEY_SRP_SERVER_INFO,
kKeyBrUlaPrefix = OT_SETTINGS_KEY_BR_ULA_PREFIX,
kKeyBrOnLinkPrefixes = OT_SETTINGS_KEY_BR_ON_LINK_PREFIXES,
kKeyBorderAgentId = OT_SETTINGS_KEY_BORDER_AGENT_ID,
kKeyTcatCommrCert = OT_SETTINGS_KEY_TCAT_COMMR_CERT,
kKeyActiveDataset = OT_SETTINGS_KEY_ACTIVE_DATASET,
kKeyPendingDataset = OT_SETTINGS_KEY_PENDING_DATASET,
kKeyNetworkInfo = OT_SETTINGS_KEY_NETWORK_INFO,
kKeyParentInfo = OT_SETTINGS_KEY_PARENT_INFO,
kKeyChildInfo = OT_SETTINGS_KEY_CHILD_INFO,
kKeySlaacIidSecretKey = OT_SETTINGS_KEY_SLAAC_IID_SECRET_KEY,
kKeySrpEcdsaKey = OT_SETTINGS_KEY_SRP_ECDSA_KEY,
kKeySrpClientInfo = OT_SETTINGS_KEY_SRP_CLIENT_INFO,
kKeySrpServerInfo = OT_SETTINGS_KEY_SRP_SERVER_INFO,
kKeyBrUlaPrefix = OT_SETTINGS_KEY_BR_ULA_PREFIX,
kKeyBrOnLinkPrefixes = OT_SETTINGS_KEY_BR_ON_LINK_PREFIXES,
kKeyBorderAgentId = OT_SETTINGS_KEY_BORDER_AGENT_ID,
kKeyTcatCommrCert = OT_SETTINGS_KEY_TCAT_COMMR_CERT,
kKeyThreadDirectKeys = OT_SETTINGS_KEY_THREAD_DIRECT_KEYS,
kKeyThreadDirectKeyRefs = OT_SETTINGS_KEY_THREAD_DIRECT_KEY_REFS,
};
static constexpr Key kLastKey = kKeyTcatCommrCert; ///< The last (numerically) enumerator value in `Key`.
static constexpr Key kLastKey = kKeyThreadDirectKeyRefs; ///< The last (numerically) enumerator value in `Key`.
static_assert(static_cast<uint16_t>(kLastKey) < static_cast<uint16_t>(OT_SETTINGS_KEY_VENDOR_RESERVED_MIN),
"Core settings keys overlap with vendor reserved keys");
@@ -682,6 +685,112 @@ public:
};
#endif
#if OT_CONFIG_THREAD_DIRECT_ENABLE
/**
* Represents a Thread Direct key info entry stored in settings.
*/
OT_TOOL_PACKED_BEGIN
class ThreadDirectKeyInfo : public Clearable<ThreadDirectKeyInfo>
{
friend class Settings;
public:
/**
* Initializes the `ThreadDirectKeyInfo` object.
*/
void Init(void) { Clear(); }
#if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
static constexpr Key kKey = kKeyThreadDirectKeys; ///< The associated key.
/**
* Gets the literal key.
*
* @returns The literal key.
*/
const ThreadDirect::LiteralKey &GetKey(void) const { return mKey; }
/**
* Sets the literal key.
*
* @param[in] aKey The literal key.
*/
void SetKey(const ThreadDirect::LiteralKey &aKey) { mKey = aKey; }
#else
static constexpr Key kKey = kKeyThreadDirectKeyRefs; ///< The associated key.
/**
* Gets the key reference (`KeyRef`).
*
* @returns The key reference.
*/
ThreadDirect::KeyRef GetKeyRef(void) const { return LittleEndian::HostSwap32(mKeyRef); }
/**
* Sets the key reference (`KeyRef`).
*
* @param[in] aKeyRef The key reference.
*/
void SetKeyRef(ThreadDirect::KeyRef aKeyRef) { mKeyRef = LittleEndian::HostSwap32(aKeyRef); }
#endif
/**
* Gets the MAC security frame counter.
*
* @returns The frame counter.
*/
uint32_t GetFrameCounter(void) const { return LittleEndian::HostSwap32(mFrameCounter); }
/**
* Sets the MAC security frame counter.
*
* @param[in] aFrameCounter The frame counter.
*/
void SetFrameCounter(uint32_t aFrameCounter) { mFrameCounter = LittleEndian::HostSwap32(aFrameCounter); }
/**
* Gets the Key ID.
*
* @returns The Key ID.
*/
const ThreadDirect::KeyId &GetKeyId(void) const { return mKeyId; }
/**
* Sets the Key ID.
*
* @param[in] aKeyId The Key ID.
*/
void SetKeyId(const ThreadDirect::KeyId &aKeyId) { mKeyId = aKeyId; }
/**
* Indicates whether the `ThreadDirectKeyInfo` matches a given `ThreadDirect::Key` (same key material and ID).
*
* @param[in] aKey The `ThreadDirect::Key` to compare with.
*
* @retval TRUE If the entry matches @p aKey.
* @retval FALSE If the entry does not match @p aKey.
*/
bool Matches(const ThreadDirect::Key &aKey) const;
private:
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
void Log(Action aAction) const;
#else
void Log(Action) const {}
#endif
#if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
ThreadDirect::LiteralKey mKey;
#else
ThreadDirect::KeyRef mKeyRef;
#endif
uint32_t mFrameCounter;
ThreadDirect::KeyId mKeyId;
} OT_TOOL_PACKED_END;
#endif // OT_CONFIG_THREAD_DIRECT_ENABLE
protected:
explicit SettingsBase(Instance &aInstance)
: InstanceLocator(aInstance)
@@ -1061,6 +1170,40 @@ public:
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
#if OT_CONFIG_THREAD_DIRECT_ENABLE
/**
* Saves or updates a Thread Direct key entry in the settings.
*
* Also updates the saved frame counter on @p aKey (advancing it by `kStoredCounterAhead`).
*
* @param[in,out] aKey The Thread Direct key to save.
*/
void SaveThreadDirectKey(ThreadDirect::Key &aKey);
/**
* Deletes a Thread Direct key entry matching @p aKey from the settings.
*
* @param[in] aKey The Thread Direct key to delete.
*/
void DeleteThreadDirectKey(const ThreadDirect::Key &aKey);
/**
* Deletes all Thread Direct key entries from the settings.
*/
void DeleteAllThreadDirectKeys(void);
/**
* Retrieves a Thread Direct key info entry at a given index from the settings.
*
* @param[in] aIndex The index to read.
* @param[out] aKeyInfo A reference to `ThreadDirectKeyInfo` to output the read value.
*
* @retval kErrorNone Successfully read the value.
* @retval kErrorNotFound No corresponding value in the setting store at @p aIndex.
*/
Error ReadThreadDirectKey(int aIndex, ThreadDirectKeyInfo &aKeyInfo) const;
#endif
private:
#if OPENTHREAD_FTD
class ChildInfoIteratorBuilder : public InstanceLocator
@@ -1082,6 +1225,10 @@ private:
void SaveEntry(Key aKey, const void *aValue, void *aPrev, uint16_t aLength);
void DeleteEntry(Key aKey);
#if OT_CONFIG_THREAD_DIRECT_ENABLE
void RemoveMatchingThreadDirectKeys(const ThreadDirect::Key &aKey, bool aKeepEntryMatchingFrameCounter);
#endif
static void Log(Action aAction, Error aError, Key aKey, const void *aValue = nullptr);
static const uint16_t kSensitiveKeys[];
+59
View File
@@ -34,6 +34,8 @@
#ifndef OT_CORE_CONFIG_THREAD_DIRECT_H_
#define OT_CORE_CONFIG_THREAD_DIRECT_H_
#include "config/border_routing.h"
/**
* @addtogroup config-thread-direct
*
@@ -61,6 +63,63 @@
#define OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE 0
#endif
/**
* @def OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE
*
* Define to 1 to allow Thread Direct `KeyManager` to allocate registered key entries from the heap.
*
* When disabled (set to 0), a fixed-size `Pool` with `OPENTHREAD_CONFIG_TD_MAX_REGISTERED_KEYS` entries is used.
*
* By default, this is enabled (set to 1) when both `OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE` and
* `OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE` are enabled, and disabled (set to 0) otherwise.
*/
#ifndef OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE && OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE
#define OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE 1
#else
#define OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE 0
#endif
#endif
/**
* @def OPENTHREAD_CONFIG_TD_MAX_REGISTERED_KEYS
*
* Specifies the maximum number of registered Thread Direct keys.
*
* When `OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE` is enabled, this limits the maximum number of heap-allocated
* key entries, and can be set to 0 to allow an unlimited number of keys. When heap allocation is disabled, this
* specifies the size of the internal `Pool` used for allocating key entries.
*
* By default, this is set to 0 (unlimited) when `OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE` is enabled, and 8
* otherwise.
*/
#ifndef OPENTHREAD_CONFIG_TD_MAX_REGISTERED_KEYS
#if OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE
#define OPENTHREAD_CONFIG_TD_MAX_REGISTERED_KEYS 0
#else
#define OPENTHREAD_CONFIG_TD_MAX_REGISTERED_KEYS 8
#endif
#endif
/**
* @def OPENTHREAD_CONFIG_TD_FRAME_COUNTER_AHEAD
*
* The value ahead of the current MAC security frame counter for persistent storage of Thread Direct keys.
*/
#ifndef OPENTHREAD_CONFIG_TD_FRAME_COUNTER_AHEAD
#define OPENTHREAD_CONFIG_TD_FRAME_COUNTER_AHEAD 1000
#endif
//----------------------------------------------------------------------------------------------------------------------
// Derived configs
#ifdef OT_CONFIG_THREAD_DIRECT_ENABLE
#error "OT_CONFIG_THREAD_DIRECT_ENABLE MUST NOT be defined directly. It is derived from other configs"
#endif
#define OT_CONFIG_THREAD_DIRECT_ENABLE \
(OPENTHREAD_CONFIG_TD_WAKE_INITIATOR_ENABLE || OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE)
/**
* @}
*/
+13
View File
@@ -178,6 +178,9 @@ Instance::Instance(void)
#if OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE
, mNetDiagClient(*this)
#endif
#if OT_CONFIG_THREAD_DIRECT_ENABLE
, mThreadDirectKeyManager(*this)
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
, mBorderAgentTxtData(*this)
, mBorderAgentManager(*this)
@@ -464,6 +467,10 @@ void Instance::AfterInit(void)
Get<Settings>().Init();
Get<Mle::Mle>().Restore();
#if OT_CONFIG_THREAD_DIRECT_ENABLE
Get<ThreadDirect::KeyManager>().RestoreKeys();
#endif
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
Get<Trel::Link>().AfterInit();
#endif
@@ -507,6 +514,9 @@ void Instance::FactoryReset(void)
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
Get<KeyManager>().DestroyTemporaryKeys();
Get<KeyManager>().DestroyPersistentKeys();
#endif
#if OT_CONFIG_THREAD_DIRECT_ENABLE
Get<ThreadDirect::KeyManager>().UnregisterAllKeys();
#endif
otPlatReset(this);
}
@@ -521,6 +531,9 @@ Error Instance::ErasePersistentInfo(void)
Get<KeyManager>().DestroyTemporaryKeys();
Get<KeyManager>().DestroyPersistentKeys();
#endif
#if OT_CONFIG_THREAD_DIRECT_ENABLE
Get<ThreadDirect::KeyManager>().UnregisterAllKeys();
#endif
exit:
return error;
+9
View File
@@ -147,6 +147,7 @@
#include "thread/time_sync_service.hpp"
#include "thread/tmf.hpp"
#include "thread/vendor_info.hpp"
#include "thread_direct/td_key_manager.hpp"
#include "utils/channel_manager.hpp"
#include "utils/channel_monitor.hpp"
#include "utils/heap.hpp"
@@ -728,6 +729,10 @@ private:
NetDiag::Client mNetDiagClient;
#endif
#if OT_CONFIG_THREAD_DIRECT_ENABLE
ThreadDirect::KeyManager mThreadDirectKeyManager;
#endif
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
MeshCoP::BorderAgent::TxtData mBorderAgentTxtData;
MeshCoP::BorderAgent::Manager mBorderAgentManager;
@@ -1136,6 +1141,10 @@ template <> inline NetDiag::Server &Instance::Get(void) { return mNetDiagServer;
template <> inline NetDiag::Client &Instance::Get(void) { return mNetDiagClient; }
#endif
#if OT_CONFIG_THREAD_DIRECT_ENABLE
template <> inline ThreadDirect::KeyManager &Instance::Get(void) { return mThreadDirectKeyManager; }
#endif
#if OPENTHREAD_CONFIG_DHCP6_CLIENT_ENABLE
template <> inline Dhcp6::Client &Instance::Get(void) { return mDhcp6Client; }
#endif
+2 -2
View File
@@ -286,7 +286,7 @@ void KeyMaterial::Clear(void)
{
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
DestroyKey();
SetKeyRef(kInvalidKeyRef);
SetKeyRefToInvalid();
#else
GetKey().Clear();
#endif
@@ -343,7 +343,7 @@ void KeyMaterial::ConvertToCryptoKey(Crypto::Key &aCryptoKey) const
void KeyMaterial::DestroyKey(void)
{
Crypto::Storage::DestroyKey(GetKeyRef());
SetKeyRef(kInvalidKeyRef);
SetKeyRefToInvalid();
}
#endif
+12 -1
View File
@@ -61,6 +61,12 @@ class InterfaceIdentifier;
}
#endif
#if OT_CONFIG_THREAD_DIRECT_ENABLE
namespace ThreadDirect {
class Key;
}
#endif
namespace Mac {
/**
@@ -544,6 +550,10 @@ typedef otMacKeyRef KeyRef;
*/
class KeyMaterial : public otMacKeyMaterial, public Unequatable<KeyMaterial>
{
#if OT_CONFIG_THREAD_DIRECT_ENABLE
friend class ot::ThreadDirect::Key;
#endif
public:
/**
* Initializes a `KeyMaterial`.
@@ -552,7 +562,7 @@ public:
{
GetKey().Clear();
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
SetKeyRef(kInvalidKeyRef);
SetKeyRefToInvalid();
#endif
}
@@ -639,6 +649,7 @@ private:
void DestroyKey(void);
void SetKeyRef(KeyRef aKeyRef) { mKeyMaterial.mKeyRef = aKeyRef; }
void SetKeyRefToInvalid(void) { SetKeyRef(kInvalidKeyRef); }
#endif
Key &GetKey(void) { return static_cast<Key &>(mKeyMaterial.mKey); }
void SetKey(const Key &aKey) { mKeyMaterial.mKey = aKey; }
+281
View File
@@ -0,0 +1,281 @@
/*
* Copyright (c) 2026, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file includes implementation of Thread Direct Key Manager.
*/
#include "td_key_manager.hpp"
#if OT_CONFIG_THREAD_DIRECT_ENABLE
#include "instance/instance.hpp"
namespace ot {
namespace ThreadDirect {
RegisterLogModule("TdKeyManager");
//---------------------------------------------------------------------------------------------------------------------
// KeyIterator
void KeyIterator::Init(Instance &aInstance) { mPtr = aInstance.Get<KeyManager>().GetKeyList().GetHead(); }
const Key *KeyIterator::GetNext(void)
{
const Key *key = static_cast<const Key *>(mPtr);
VerifyOrExit(key != nullptr);
mPtr = key->GetNext();
exit:
return key;
}
//---------------------------------------------------------------------------------------------------------------------
// KeyManager
KeyManager::KeyManager(Instance &aInstance)
: InstanceLocator(aInstance)
, mStoreTask(aInstance)
{
}
#if OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE
KeyManager::~KeyManager(void)
{
Key *key;
while ((key = mKeys.Pop()) != nullptr)
{
key->Free();
}
}
#endif
Key *KeyManager::AllocateKey(void)
{
Key *key;
#if OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE
if ((kMaxKeys != 0) && (mKeys.CountAllEntries() >= kMaxKeys))
{
key = nullptr;
}
else
{
key = Key::Allocate();
}
#else
key = mPool.Allocate();
#endif
return key;
}
void KeyManager::FreeKey(Key &aKey)
{
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
// A `KeyRef` can be shared across multiple registered `Key` entries
// with different Key IDs. If another entry in `mKeys` is still
// using the same `KeyRef`, reset `aKey`'s `KeyRef` to invalid
// before calling `Clear()` so that `Clear()` does not destroy the
// shared `KeyRef` in platform crypto storage.
if (mKeys.ContainsMatching(aKey.GetKeyMaterial().GetKeyRef()))
{
aKey.SetKeyRefToInvalid();
}
#endif
aKey.mKeyMaterial.Clear();
#if OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE
aKey.Free();
#else
mPool.Free(aKey);
#endif
}
Key *KeyManager::RegisterKey(const KeyMaterial &aKeyMaterial, const KeyId &aKeyId)
{
Key *key = FindKey(aKeyMaterial, aKeyId);
VerifyOrExit(key == nullptr);
key = AllocateKey();
VerifyOrExit(key != nullptr);
key->Init(GetInstance(), aKeyId, /* aFrameCounter */ 0);
key->SetKeyMaterial(aKeyMaterial);
mKeys.Push(*key);
Get<Settings>().SaveThreadDirectKey(*key);
Log(kActionRegister, *key);
exit:
return key;
}
Key *KeyManager::FindKey(const KeyMaterial &aKeyMaterial, const KeyId &aKeyId)
{
return mKeys.FindMatching(aKeyMaterial, aKeyId);
}
Key *KeyManager::FindKey(const Settings::ThreadDirectKeyInfo &aKeyInfo)
{
Key *matchedKey = nullptr;
for (Key &key : mKeys)
{
if (aKeyInfo.Matches(key))
{
matchedKey = &key;
break;
}
}
return matchedKey;
}
void KeyManager::UnregisterKey(Key &aKey)
{
SuccessOrAssert(mKeys.Remove(aKey));
Get<Settings>().DeleteThreadDirectKey(aKey);
Log(kActionUnregister, aKey);
FreeKey(aKey);
}
void KeyManager::UnregisterAllKeys(void)
{
Key *key;
LogInfo("Unregistering all keys");
while ((key = mKeys.Pop()) != nullptr)
{
FreeKey(*key);
}
Get<Settings>().DeleteAllThreadDirectKeys();
}
void KeyManager::RestoreKeys(void)
{
Key *key;
Settings::ThreadDirectKeyInfo keyInfo;
for (int index = 0;; index++)
{
SuccessOrExit(Get<Settings>().ReadThreadDirectKey(index, keyInfo));
key = FindKey(keyInfo);
if (key != nullptr)
{
// Multiple entries for the same key may exist in settings if a
// reset occurred while `SaveThreadDirectKey()` was adding a new
// entry and before the older entry was removed. Keep the highest
// frame counter.
if (keyInfo.GetFrameCounter() > key->GetFrameCounter())
{
key->SetFrameCounters(keyInfo.GetFrameCounter());
Log(kActionRestoreNewCounter, *key);
}
continue;
}
key = AllocateKey();
if (key == nullptr)
{
LogWarn("Could not restore - too many saved keys (kMaxKeys:%u)", kMaxKeys);
ExitNow();
}
key->Init(GetInstance(), keyInfo.GetKeyId(), keyInfo.GetFrameCounter());
#if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
key->SetKeyMaterial(keyInfo.GetKey());
#else
key->SetKeyMaterial(keyInfo.GetKeyRef());
#endif
mKeys.Push(*key);
Log(kActionRestore, *key);
}
exit:
return;
}
void KeyManager::StoreKeys(void)
{
for (Key &key : mKeys)
{
if (key.ShouldUpdateSavedFrameCounter())
{
Get<Settings>().SaveThreadDirectKey(key);
}
}
}
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
const char *KeyManager::ActionToString(Action aAction)
{
#define ActionMapList(_) \
_(kActionRegister, "Registered") \
_(kActionUnregister, "Unregistered") \
_(kActionRestore, "Restored") \
_(kActionRestoreNewCounter, "Restored (new counter)")
DefineEnumStringArray(ActionMapList);
return kStrings[aAction];
}
void KeyManager::Log(Action aAction, const Key &aKey)
{
LogInfo("%s key {id:%s, ctr:0x%lx, savedctr:0x%lx}", ActionToString(aAction), aKey.GetId().ToString().AsCString(),
ToUlong(aKey.GetFrameCounter()), ToUlong(aKey.GetSavedFrameCounter()));
}
#endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
} // namespace ThreadDirect
} // namespace ot
#endif // OT_CONFIG_THREAD_DIRECT_ENABLE
+195
View File
@@ -0,0 +1,195 @@
/*
* Copyright (c) 2026, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file includes definitions for Thread Direct Key Manager
*/
#ifndef OT_CORE_THREAD_DIRECT_TD_KEY_MANAGER_HPP_
#define OT_CORE_THREAD_DIRECT_TD_KEY_MANAGER_HPP_
#include "openthread-core-config.h"
#include "common/as_core_type.hpp"
#include "common/linked_list.hpp"
#include "common/locator.hpp"
#include "common/log.hpp"
#include "common/non_copyable.hpp"
#include "common/pool.hpp"
#include "common/settings.hpp"
#include "common/tasklet.hpp"
#include "thread_direct/td_types.hpp"
#if OT_CONFIG_THREAD_DIRECT_ENABLE
namespace ot {
namespace ThreadDirect {
/**
* Represents an iterator for iterating through registered Thread Direct keys.
*/
class KeyIterator : public otThreadDirectKeyIterator
{
public:
/**
* Initializes the `KeyIterator` to iterate over registered Thread Direct keys.
*
* @param[in] aInstance A reference to the OpenThread instance.
*/
void Init(Instance &aInstance);
/**
* Gets the next registered Thread Direct key in the list and advances the iterator.
*
* @returns A pointer to the next `Key` entry, or `nullptr` if the end of the list is reached.
*/
const Key *GetNext(void);
};
/**
* Manages registered Thread Direct keys and their persistent storage in settings.
*/
class KeyManager : public InstanceLocator, private NonCopyable
{
friend class ot::Instance;
friend class Key;
public:
/**
* Represents the linked list of registered Thread Direct keys.
*/
typedef LinkedList<Key> KeyList;
/**
* Initializes the `KeyManager` object.
*
* @param[in] aInstance A reference to the OpenThread instance.
*/
explicit KeyManager(Instance &aInstance);
#if OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE
/**
* Destroys the `KeyManager` object and frees any heap-allocated `Key` entries.
*/
~KeyManager(void);
#endif
/**
* Registers a Thread Direct key and its associated Key ID.
*
* If a matching key with the same @p aKeyMaterial and @p aKeyId is already registered, a pointer to the existing
* `Key` entry is returned. Otherwise, a new `Key` entry is allocated, initialized, and saved in persistent
* settings.
*
* @param[in] aKeyMaterial The key material to register.
* @param[in] aKeyId The Key ID to register.
*
* @returns A pointer to the registered `Key` entry, or `nullptr` if a new entry could not be allocated.
*/
Key *RegisterKey(const KeyMaterial &aKeyMaterial, const KeyId &aKeyId);
/**
* Finds a registered Thread Direct key matching a given key material and Key ID.
*
* @param[in] aKeyMaterial The key material to match.
* @param[in] aKeyId The Key ID to match.
*
* @returns A pointer to the matching `Key` entry, or `nullptr` if no match is found.
*/
Key *FindKey(const KeyMaterial &aKeyMaterial, const KeyId &aKeyId);
/**
* Unregisters a given Thread Direct key, removes it from persistent settings, and frees its entry.
*
* @param[in] aKey The registered `Key` to unregister.
*/
void UnregisterKey(Key &aKey);
/**
* Unregisters all Thread Direct keys, removes them from persistent settings, and frees their entries.
*/
void UnregisterAllKeys(void);
/**
* Gets the list of registered Thread Direct keys.
*
* @returns A constant reference to the `KeyList`.
*/
const KeyList &GetKeyList(void) const { return mKeys; }
/**
* Gets the list of registered Thread Direct keys.
*
* @returns A reference to the `KeyList`.
*/
KeyList &GetKeyList(void) { return mKeys; }
private:
static constexpr uint16_t kMaxKeys = OPENTHREAD_CONFIG_TD_MAX_REGISTERED_KEYS;
enum Action : uint8_t // Used in `Log()`.
{
kActionRegister,
kActionUnregister,
kActionRestore,
kActionRestoreNewCounter,
};
Key *AllocateKey(void);
void FreeKey(Key &aKey);
Key *FindKey(const Settings::ThreadDirectKeyInfo &aKeyInfo);
void RestoreKeys(void);
void StoreKeys(void);
void PostStoreTask(void) { mStoreTask.Post(); }
#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
static const char *ActionToString(Action aAction);
void Log(Action aAction, const Key &aKey);
#else
void Log(Action, const Key &) {}
#endif
using StoreTask = TaskletIn<KeyManager, &KeyManager::StoreKeys>;
LinkedList<Key> mKeys;
StoreTask mStoreTask;
#if !OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE
Pool<Key, kMaxKeys> mPool;
#endif
};
} // namespace ThreadDirect
DefineCoreType(otThreadDirectKeyIterator, ThreadDirect::KeyIterator);
} // namespace ot
#endif // OT_CONFIG_THREAD_DIRECT_ENABLE
#endif // OT_CORE_THREAD_DIRECT_TD_KEY_MANAGER_HPP_
+106
View File
@@ -0,0 +1,106 @@
/*
* Copyright (c) 2026, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file includes implementation of Thread Direct.
*/
#include "td_types.hpp"
#include "instance/instance.hpp"
#if OT_CONFIG_THREAD_DIRECT_ENABLE
namespace ot {
namespace ThreadDirect {
//---------------------------------------------------------------------------------------------------------------------
// KeyId
KeyId::InfoString KeyId::ToString(void) const
{
InfoString string;
string.AppendHexBytes(GetBytes(), kSize);
return string;
}
//---------------------------------------------------------------------------------------------------------------------
// Key
void Key::Init(Instance &aInstance, const KeyId &aId, uint32_t aFrameCounter)
{
InstanceLocatorInit::Init(aInstance);
mId = aId;
SetFrameCounters(aFrameCounter);
}
void Key::SetFrameCounters(uint32_t aFrameCounter)
{
mFrameCounter = aFrameCounter;
mSavedFrameCounter = aFrameCounter;
}
void Key::IncrementFrameCounter(void)
{
if (mFrameCounter != NumericLimits<uint32_t>::kMax)
{
mFrameCounter++;
}
if (ShouldUpdateSavedFrameCounter())
{
Get<KeyManager>().PostStoreTask();
}
}
bool Key::ShouldUpdateSavedFrameCounter(void) const
{
return (mFrameCounter >= mSavedFrameCounter) && (mSavedFrameCounter != NumericLimits<uint32_t>::kMax);
}
uint32_t Key::UpdateSavedFrameCounter(void)
{
if (CanAddSafely(mFrameCounter, kStoredCounterAhead))
{
mSavedFrameCounter = mFrameCounter + kStoredCounterAhead;
}
else
{
SetToUintMax(mSavedFrameCounter);
}
return mSavedFrameCounter;
}
} // namespace ThreadDirect
} // namespace ot
#endif // OT_CONFIG_THREAD_DIRECT_ENABLE
+206
View File
@@ -0,0 +1,206 @@
/*
* Copyright (c) 2026, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file includes definitions for Thread Direct related types.
*/
#ifndef OT_CORE_THREAD_DIRECT_TD_TYPES_HPP_
#define OT_CORE_THREAD_DIRECT_TD_TYPES_HPP_
#include "openthread-core-config.h"
#include <openthread/thread_direct.h>
#include "common/as_core_type.hpp"
#include "common/equatable.hpp"
#include "common/heap_allocatable.hpp"
#include "common/linked_list.hpp"
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
#include "mac/mac_types.hpp"
//----------------------------------------------------------------------------------------------------------------------
#if OT_CONFIG_THREAD_DIRECT_ENABLE
struct otThreadDirectKey
{
};
namespace ot {
class Settings;
namespace ThreadDirect {
/**
* Type alias for a cryptographic key reference (`otCryptoKeyRef`).
*/
typedef Mac::KeyRef KeyRef;
/**
* Type alias for a literal 16-byte MAC key.
*/
typedef Mac::Key LiteralKey;
/**
* Type alias for Thread Direct key material (literal key or key reference).
*/
typedef Mac::KeyMaterial KeyMaterial;
/**
* Represents a Thread Direct Key ID.
*/
OT_TOOL_PACKED_BEGIN
class KeyId : public otThreadDirectKeyId, public Equatable<KeyId>
{
public:
static constexpr uint16_t kSize = OT_THREAD_DIRECT_KEY_ID_SIZE; ///< Size of a Thread Direct Key ID in bytes.
static constexpr uint16_t kInfoStringSize = 2 * kSize + 1; ///< Max chars for the info string (`ToString()`).
/**
* Defines the fixed-length `String` object returned from `ToString()`.
*/
typedef String<kInfoStringSize> InfoString;
/**
* Gets the Key ID bytes.
*
* @returns A pointer to the Key ID byte array of `kSize` bytes.
*/
const uint8_t *GetBytes(void) const { return m8; }
/**
* Converts the Key ID to a hex string.
*
* @returns An `InfoString` representing the Key ID in hex format.
*/
InfoString ToString(void) const;
} OT_TOOL_PACKED_END;
class KeyManager;
/**
* Represents a registered Thread Direct key entry.
*/
class Key : public otThreadDirectKey,
public InstanceLocatorInit,
public LinkedListEntry<Key>,
#if OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE
public Heap::Allocatable<Key>,
#endif
private NonCopyable
{
friend class KeyManager;
friend class LinkedListEntry<Key>;
friend class LinkedList<Key>;
friend class ot::Settings;
public:
/**
* Gets the key material associated with the Thread Direct key.
*
* @returns A reference to the `KeyMaterial`.
*/
const KeyMaterial &GetKeyMaterial(void) const { return mKeyMaterial; }
/**
* Gets the Key ID associated with the Thread Direct key.
*
* @returns A reference to the `KeyId`.
*/
const KeyId &GetId(void) const { return mId; }
/**
* Indicates whether the Thread Direct key has a given Key ID.
*
* @param[in] aId The Key ID to check against.
*
* @retval TRUE If the key's ID matches @p aId.
* @retval FALSE If the key's ID does not match @p aId.
*/
bool HasId(const KeyId &aId) const { return mId == aId; }
/**
* Gets the current MAC security frame counter associated with the Thread Direct key.
*
* @returns The current MAC security frame counter.
*/
uint32_t GetFrameCounter(void) const { return mFrameCounter; }
/**
* Increments the MAC security frame counter associated with the Thread Direct key.
*
* If the incremented counter reaches or exceeds the stored counter in non-volatile settings, a tasklet is posted
* to update the persisted frame counter in settings.
*/
void IncrementFrameCounter(void);
private:
static constexpr uint32_t kStoredCounterAhead = OPENTHREAD_CONFIG_TD_FRAME_COUNTER_AHEAD;
void Init(Instance &aInstance, const KeyId &aId, uint32_t aFrameCounter);
void SetKeyMaterial(const KeyMaterial &aKeyMaterial) { mKeyMaterial = aKeyMaterial; }
#if !OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
void SetKeyMaterial(const LiteralKey &aLiteralKey) { mKeyMaterial.SetKey(aLiteralKey); }
#else
void SetKeyMaterial(KeyRef aKeyRef) { mKeyMaterial.SetKeyRef(aKeyRef); }
void SetKeyRefToInvalid(void) { mKeyMaterial.SetKeyRefToInvalid(); }
bool Matches(KeyRef aKeyRef) const { return mKeyMaterial.GetKeyRef() == aKeyRef; }
#endif
bool Matches(const KeyMaterial &aKeyMaterial, const KeyId &aId) const
{
return (mKeyMaterial == aKeyMaterial) && HasId(aId);
}
void SetFrameCounters(uint32_t aFrameCounter);
uint32_t GetSavedFrameCounter(void) const { return mSavedFrameCounter; }
bool ShouldUpdateSavedFrameCounter(void) const;
uint32_t UpdateSavedFrameCounter(void);
Key *mNext;
KeyMaterial mKeyMaterial;
KeyId mId;
uint32_t mFrameCounter;
uint32_t mSavedFrameCounter;
};
} // namespace ThreadDirect
DefineCoreType(otThreadDirectKeyId, ThreadDirect::KeyId);
DefineCoreType(otThreadDirectKey, ThreadDirect::Key);
} // namespace ot
#endif // OT_CONFIG_THREAD_DIRECT_ENABLE
#endif // OT_CORE_THREAD_DIRECT_TD_TYPES_HPP_
+1
View File
@@ -463,6 +463,7 @@ ot_nexus_test(srp_scale "core;nexus")
ot_nexus_test(srp_server_anycast_mode "core;nexus")
ot_nexus_test(srp_server_reboot_port "core;nexus")
ot_nexus_test(srp_ttl "core;nexus")
ot_nexus_test(thread_direct_key_manager "core;nexus")
ot_nexus_test(tmf_origin "core;nexus")
ot_nexus_test(zero_len_external_route "core;nexus")
@@ -152,6 +152,10 @@
#define OPENTHREAD_CONFIG_SRP_SERVER_ADVERTISING_PROXY_ENABLE 1
#define OPENTHREAD_CONFIG_SRP_SERVER_ENABLE 1
#define OPENTHREAD_CONFIG_TCP_ENABLE 1
#define OPENTHREAD_CONFIG_TD_WAKE_INITIATOR_ENABLE 1
#define OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE 1
#define OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE 1
#define OPENTHREAD_CONFIG_TD_MAX_REGISTERED_KEYS 8
#define OPENTHREAD_CONFIG_TLS_ENABLE 0
#define OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_ENTRIES 256
#define OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_MAX_SNOOP_ENTRIES 16
@@ -0,0 +1,451 @@
/*
* Copyright (c) 2026, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <string.h>
#include <openthread/thread_direct.h>
#include <openthread/platform/settings.h>
#include "platform/nexus_core.hpp"
#include "platform/nexus_node.hpp"
namespace ot {
namespace Nexus {
using Key = ThreadDirect::Key;
using KeyId = ThreadDirect::KeyId;
using KeyIterator = ThreadDirect::KeyIterator;
using KeyManager = ThreadDirect::KeyManager;
using KeyMaterial = ThreadDirect::KeyMaterial;
using ThreadDirectKeyInfo = ot::Settings::ThreadDirectKeyInfo;
static void MakeKeyMaterial(otThreadDirectKeyMaterial &aKeyMaterial, uint8_t aSeed)
{
ClearAllBytes(aKeyMaterial);
for (uint16_t i = 0; i < OT_THREAD_DIRECT_KEY_SIZE; i++)
{
aKeyMaterial.mKeyMaterial.mKey.m8[i] = static_cast<uint8_t>(aSeed + i);
}
}
static void MakeKeyId(otThreadDirectKeyId &aKeyId, uint8_t aSeed)
{
for (uint16_t i = 0; i < OT_THREAD_DIRECT_KEY_ID_SIZE; i++)
{
aKeyId.m8[i] = static_cast<uint8_t>((aSeed << 4) + i + 1);
}
}
static uint16_t CountKeysViaIterator(otInstance *aInstance)
{
otThreadDirectKeyIterator iter;
uint16_t count = 0;
otThreadDirectInitKeyIterator(aInstance, &iter);
while (otThreadDirectGetNextKey(&iter) != nullptr)
{
count++;
}
return count;
}
static void VerifyKeyProperties(const otThreadDirectKey *aKey,
const otThreadDirectKeyMaterial &aExpectedMaterial,
const otThreadDirectKeyId &aExpectedId,
uint32_t aExpectedFrameCounter)
{
const otThreadDirectKeyMaterial *material;
const otThreadDirectKeyId *id;
VerifyOrQuit(aKey != nullptr);
material = otThreadDirectKeyGetMaterial(aKey);
VerifyOrQuit(material != nullptr);
VerifyOrQuit(AsCoreType(material) == AsCoreType(&aExpectedMaterial));
id = otThreadDirectKeyGetId(aKey);
VerifyOrQuit(id != nullptr);
VerifyOrQuit(AsCoreType(id) == AsCoreType(&aExpectedId));
VerifyOrQuit(AsCoreType(aKey).HasId(AsCoreType(&aExpectedId)));
VerifyOrQuit(otThreadDirectKeyGetFrameCounter(aKey) == aExpectedFrameCounter);
}
void TestThreadDirectKeyManager(void)
{
static constexpr uint16_t kMaxKeys = OPENTHREAD_CONFIG_TD_MAX_REGISTERED_KEYS;
static constexpr uint32_t kStoredCounterAhead = OPENTHREAD_CONFIG_TD_FRAME_COUNTER_AHEAD;
Core nexus;
Node &node0 = nexus.CreateNode();
otInstance *instance;
otThreadDirectKeyMaterial materials[kMaxKeys + 1];
otThreadDirectKeyId ids[kMaxKeys + 1];
const otThreadDirectKey *keys[kMaxKeys + 1];
const otThreadDirectKey *key;
otThreadDirectKeyIterator iter;
Log("------------------------------------------------------------------------------------------------------");
Log("TestThreadDirectKeyManager");
nexus.AdvanceTime(0);
instance = &node0.GetInstance();
SuccessOrQuit(node0.SetLogLevel(kLogLevelInfo));
for (uint16_t i = 0; i <= kMaxKeys; i++)
{
MakeKeyMaterial(materials[i], static_cast<uint8_t>(i + 1));
MakeKeyId(ids[i], static_cast<uint8_t>(i + 1));
keys[i] = nullptr;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check initial state (empty key list)");
VerifyOrQuit(node0.Get<KeyManager>().GetKeyList().IsEmpty());
VerifyOrQuit(CountKeysViaIterator(instance) == 0);
otThreadDirectInitKeyIterator(instance, &iter);
VerifyOrQuit(otThreadDirectGetNextKey(&iter) == nullptr);
VerifyOrQuit(otThreadDirectGetNextKey(&iter) == nullptr);
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[0], &ids[0]) == nullptr);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Register keys, including keys sharing the same material or same Key ID");
// Key 0: (materials[0], ids[0])
keys[0] = otThreadDirectRegisterKey(instance, &materials[0], &ids[0]);
VerifyKeyProperties(keys[0], materials[0], ids[0], 0);
VerifyOrQuit(CountKeysViaIterator(instance) == 1);
// Key 1: (materials[1], ids[1])
keys[1] = otThreadDirectRegisterKey(instance, &materials[1], &ids[1]);
VerifyKeyProperties(keys[1], materials[1], ids[1], 0);
VerifyOrQuit(keys[1] != keys[0]);
VerifyOrQuit(CountKeysViaIterator(instance) == 2);
// Key 2: same key material as Key 0, but different Key ID (materials[0], ids[2])
keys[2] = otThreadDirectRegisterKey(instance, &materials[0], &ids[2]);
VerifyKeyProperties(keys[2], materials[0], ids[2], 0);
VerifyOrQuit(keys[2] != keys[0]);
VerifyOrQuit(!AsCoreType(keys[2]).HasId(AsCoreType(&ids[0])));
VerifyOrQuit(CountKeysViaIterator(instance) == 3);
// Key 3: different key material, but same Key ID as Key 0 (materials[3], ids[0])
keys[3] = otThreadDirectRegisterKey(instance, &materials[3], &ids[0]);
VerifyKeyProperties(keys[3], materials[3], ids[0], 0);
VerifyOrQuit(keys[3] != keys[0]);
VerifyOrQuit(CountKeysViaIterator(instance) == 4);
// Verify `otThreadDirectFindKey()` matches both key material and Key ID
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[0], &ids[0]) == keys[0]);
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[1], &ids[1]) == keys[1]);
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[0], &ids[2]) == keys[2]);
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[3], &ids[0]) == keys[3]);
// Mismatched combinations must return `nullptr`
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[0], &ids[1]) == nullptr);
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[1], &ids[0]) == nullptr);
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[3], &ids[2]) == nullptr);
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[2], &ids[2]) == nullptr);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check re-registering an already registered key returns the existing entry");
AsNonConst(AsCoreType(keys[0])).IncrementFrameCounter();
VerifyOrQuit(otThreadDirectKeyGetFrameCounter(keys[0]) == 1);
key = otThreadDirectRegisterKey(instance, &materials[0], &ids[0]);
VerifyOrQuit(key == keys[0]);
VerifyOrQuit(otThreadDirectKeyGetFrameCounter(key) == 1);
VerifyOrQuit(CountKeysViaIterator(instance) == 4);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check iterator traversal and re-initialization");
{
bool found[4] = {false, false, false, false};
otThreadDirectInitKeyIterator(instance, &iter);
while ((key = otThreadDirectGetNextKey(&iter)) != nullptr)
{
bool matched = false;
for (uint16_t i = 0; i < 4; i++)
{
if (key == keys[i])
{
VerifyOrQuit(!found[i]);
found[i] = true;
matched = true;
break;
}
}
VerifyOrQuit(matched);
}
for (bool isFound : found)
{
VerifyOrQuit(isFound);
}
// Re-initialize iterator and verify it starts from the head again
otThreadDirectInitKeyIterator(instance, &iter);
VerifyOrQuit(otThreadDirectGetNextKey(&iter) != nullptr);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Register up to kMaxKeys (%u) and verify limit enforcement", kMaxKeys);
for (uint16_t i = 4; i < kMaxKeys; i++)
{
keys[i] = otThreadDirectRegisterKey(instance, &materials[i], &ids[i]);
VerifyKeyProperties(keys[i], materials[i], ids[i], 0);
}
VerifyOrQuit(CountKeysViaIterator(instance) == kMaxKeys);
// Attempting to register one more key beyond `kMaxKeys` must fail and return `nullptr`
VerifyOrQuit(otThreadDirectRegisterKey(instance, &materials[kMaxKeys], &ids[kMaxKeys]) == nullptr);
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[kMaxKeys], &ids[kMaxKeys]) == nullptr);
VerifyOrQuit(CountKeysViaIterator(instance) == kMaxKeys);
// Re-registering an existing key when at `kMaxKeys` must still succeed and return the existing entry
VerifyOrQuit(otThreadDirectRegisterKey(instance, &materials[2], &ids[2]) == nullptr);
VerifyOrQuit(otThreadDirectRegisterKey(instance, &materials[0], &ids[2]) == keys[2]);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Unregister a key to free a slot and register a new key");
otThreadDirectUnregisterKey(instance, keys[2]);
keys[2] = nullptr;
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[0], &ids[2]) == nullptr);
VerifyOrQuit(CountKeysViaIterator(instance) == kMaxKeys - 1);
// Registering a new key now succeeds
keys[2] = otThreadDirectRegisterKey(instance, &materials[2], &ids[2]);
VerifyKeyProperties(keys[2], materials[2], ids[2], 0);
VerifyOrQuit(CountKeysViaIterator(instance) == kMaxKeys);
// Unregister keys[3..kMaxKeys-1] to leave 4 distinct keys (keys[0], keys[1], keys[2], keys[4]) for reset tests
otThreadDirectUnregisterKey(instance, keys[3]);
keys[3] = nullptr;
for (uint16_t i = 5; i < kMaxKeys; i++)
{
otThreadDirectUnregisterKey(instance, keys[i]);
keys[i] = nullptr;
}
VerifyOrQuit(CountKeysViaIterator(instance) == 4);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Advance frame counters on keys and verify persistence across reset");
// Reset keys[0] by unregistering and re-registering so it starts cleanly at frame counter 0
otThreadDirectUnregisterKey(instance, keys[0]);
keys[0] = otThreadDirectRegisterKey(instance, &materials[0], &ids[0]);
VerifyKeyProperties(keys[0], materials[0], ids[0], 0);
// Key 0: frame counter stays at 0 (saved counter in settings is kStoredCounterAhead = 1000)
// Key 1: increment frame counter 500 times (< kStoredCounterAhead, so saved counter remains 1000)
for (uint32_t i = 0; i < kStoredCounterAhead / 2; i++)
{
AsNonConst(AsCoreType(keys[1])).IncrementFrameCounter();
}
VerifyOrQuit(otThreadDirectKeyGetFrameCounter(keys[1]) == kStoredCounterAhead / 2);
// Key 2: increment frame counter to kStoredCounterAhead (1000), let store task run, then add 25 more (1025).
// Saved counter in settings should be updated to 2 * kStoredCounterAhead (2000).
for (uint32_t i = 0; i < kStoredCounterAhead; i++)
{
AsNonConst(AsCoreType(keys[2])).IncrementFrameCounter();
}
nexus.AdvanceTime(0);
for (uint32_t i = 0; i < 25; i++)
{
AsNonConst(AsCoreType(keys[2])).IncrementFrameCounter();
}
VerifyOrQuit(otThreadDirectKeyGetFrameCounter(keys[2]) == kStoredCounterAhead + 25);
// Key 4: increment frame counter 2500 times (processing tasklets along the way).
// Saved counter in settings should advance to 2000 at 1000, and to 3000 at 2000.
for (uint32_t i = 0; i < 2 * kStoredCounterAhead + 500; i++)
{
AsNonConst(AsCoreType(keys[4])).IncrementFrameCounter();
nexus.AdvanceTime(0);
}
VerifyOrQuit(otThreadDirectKeyGetFrameCounter(keys[4]) == 2 * kStoredCounterAhead + 500);
// Reset node0 and verify all 4 keys and their stored frame counters are restored from settings
node0.Reset();
nexus.AdvanceTime(0);
instance = &node0.GetInstance();
VerifyOrQuit(CountKeysViaIterator(instance) == 4);
keys[0] = otThreadDirectFindKey(instance, &materials[0], &ids[0]);
keys[1] = otThreadDirectFindKey(instance, &materials[1], &ids[1]);
keys[2] = otThreadDirectFindKey(instance, &materials[2], &ids[2]);
keys[4] = otThreadDirectFindKey(instance, &materials[4], &ids[4]);
VerifyKeyProperties(keys[0], materials[0], ids[0], kStoredCounterAhead);
VerifyKeyProperties(keys[1], materials[1], ids[1], kStoredCounterAhead);
VerifyKeyProperties(keys[2], materials[2], ids[2], 2 * kStoredCounterAhead);
VerifyKeyProperties(keys[4], materials[4], ids[4], 3 * kStoredCounterAhead);
// Verify previously unregistered keys were not restored
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[3], &ids[0]) == nullptr);
VerifyOrQuit(otThreadDirectFindKey(instance, &materials[5], &ids[5]) == nullptr);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check first frame counter increment after reset immediately triggers a settings update");
// After `RestoreKeys()`, `mSavedFrameCounter == mFrameCounter` (1000 for keys[0]).
// Incrementing `keys[0]` once makes `mFrameCounter == 1001 >= mSavedFrameCounter`, posting `mStoreTask`.
// When `mStoreTask` runs, `SaveThreadDirectKey()` saves `1001 + kStoredCounterAhead` (2001).
AsNonConst(AsCoreType(keys[0])).IncrementFrameCounter();
VerifyOrQuit(otThreadDirectKeyGetFrameCounter(keys[0]) == kStoredCounterAhead + 1);
nexus.AdvanceTime(0);
node0.Reset();
nexus.AdvanceTime(0);
instance = &node0.GetInstance();
keys[0] = otThreadDirectFindKey(instance, &materials[0], &ids[0]);
VerifyKeyProperties(keys[0], materials[0], ids[0], 2 * kStoredCounterAhead + 1);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check RestoreKeys() deduplicates multiple settings entries for the same key and keeps highest counter");
{
ThreadDirectKeyInfo dupKeyInfo;
dupKeyInfo.Init();
dupKeyInfo.SetKey(AsCoreType(keys[0]).GetKeyMaterial().GetKey());
dupKeyInfo.SetKeyId(AsCoreType(&ids[0]));
// Inject a duplicate entry with a higher frame counter (50000) and one with a lower frame counter (500)
dupKeyInfo.SetFrameCounter(50000);
SuccessOrQuit(otPlatSettingsAdd(instance, ThreadDirectKeyInfo::kKey,
reinterpret_cast<const uint8_t *>(&dupKeyInfo), sizeof(ThreadDirectKeyInfo)));
dupKeyInfo.SetFrameCounter(500);
SuccessOrQuit(otPlatSettingsAdd(instance, ThreadDirectKeyInfo::kKey,
reinterpret_cast<const uint8_t *>(&dupKeyInfo), sizeof(ThreadDirectKeyInfo)));
}
node0.Reset();
nexus.AdvanceTime(0);
instance = &node0.GetInstance();
// Must still have only 4 unique keys, and keys[0] must have the highest frame counter (50000)
VerifyOrQuit(CountKeysViaIterator(instance) == 4);
keys[0] = otThreadDirectFindKey(instance, &materials[0], &ids[0]);
VerifyKeyProperties(keys[0], materials[0], ids[0], 50000);
// Incrementing keys[0] triggers `SaveThreadDirectKey()`, which removes all stale duplicate entries in settings
AsNonConst(AsCoreType(keys[0])).IncrementFrameCounter();
nexus.AdvanceTime(0);
node0.Reset();
nexus.AdvanceTime(0);
instance = &node0.GetInstance();
VerifyOrQuit(CountKeysViaIterator(instance) == 4);
keys[0] = otThreadDirectFindKey(instance, &materials[0], &ids[0]);
VerifyKeyProperties(keys[0], materials[0], ids[0], 50001 + kStoredCounterAhead);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check otThreadDirectUnregisterAllKeys() removes all keys from memory and persistent settings");
otThreadDirectUnregisterAllKeys(instance);
VerifyOrQuit(CountKeysViaIterator(instance) == 0);
VerifyOrQuit(node0.Get<KeyManager>().GetKeyList().IsEmpty());
node0.Reset();
nexus.AdvanceTime(0);
instance = &node0.GetInstance();
VerifyOrQuit(CountKeysViaIterator(instance) == 0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check otInstanceErasePersistentInfo() unregisters all keys and wipes settings");
keys[0] = otThreadDirectRegisterKey(instance, &materials[0], &ids[0]);
keys[1] = otThreadDirectRegisterKey(instance, &materials[1], &ids[1]);
VerifyOrQuit(CountKeysViaIterator(instance) == 2);
SuccessOrQuit(otInstanceErasePersistentInfo(instance));
VerifyOrQuit(CountKeysViaIterator(instance) == 0);
node0.Reset();
nexus.AdvanceTime(0);
instance = &node0.GetInstance();
VerifyOrQuit(CountKeysViaIterator(instance) == 0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check otInstanceFactoryReset() unregisters all keys and wipes settings");
keys[0] = otThreadDirectRegisterKey(instance, &materials[0], &ids[0]);
keys[1] = otThreadDirectRegisterKey(instance, &materials[1], &ids[1]);
VerifyOrQuit(CountKeysViaIterator(instance) == 2);
otInstanceFactoryReset(instance);
VerifyOrQuit(CountKeysViaIterator(instance) == 0);
node0.Reset();
nexus.AdvanceTime(0);
instance = &node0.GetInstance();
VerifyOrQuit(CountKeysViaIterator(instance) == 0);
Log("TestThreadDirectKeyManager passed successfully!");
}
} // namespace Nexus
} // namespace ot
int main(void)
{
ot::Nexus::TestThreadDirectKeyManager();
printf("All tests passed\n");
return 0;
}
@@ -89,6 +89,10 @@
// them. The `toranj` build under the simulation platform covers the
// opposite/alternative configurations (e.g., allows `USE_HEAP`).
#define OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE 0
#define OPENTHREAD_CONFIG_TD_MAX_REGISTERED_KEYS 8
#define OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE 0
#define OPENTHREAD_CONFIG_TREL_USE_HEAP_ENABLE 1
@@ -108,6 +108,10 @@
#define OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE 1
#define OPENTHREAD_CONFIG_TD_KEY_LIST_USE_HEAP_ENABLE 1
#define OPENTHREAD_CONFIG_TD_MAX_REGISTERED_KEYS 8
#define OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE 1
#define OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE 1
@@ -111,6 +111,10 @@
#define OPENTHREAD_CONFIG_MESSAGE_USE_HEAP_ENABLE 0
#define OPENTHREAD_CONFIG_TD_WAKE_INITIATOR_ENABLE 1
#define OPENTHREAD_CONFIG_TD_WAKE_LISTENER_ENABLE 1
#define OPENTHREAD_CONFIG_TMF_ADDRESS_CACHE_ENTRIES 16
#define OPENTHREAD_CONFIG_TMF_ADDRESS_QUERY_TIMEOUT 6