[history-tracker] add client/server for remote query (#11757)

This commit introduces a client/server mechanism to the History
Tracker module. This allows a device to query history information
from another device over the Thread network using TMF messages.

The new functionality is composed of three main parts:

- Server (`HistoryTracker::Server`): This component is responsible for
  handling incoming TMF query requests (`h/qy`). It collects the
  requested local history entries (e.g., Network Info), formats them
  into TLVs, and sends them back to the requester in one or more TMF
  answer messages (`h/an`). It can fragment large responses into
  multiple messages.

- Client (`HistoryTracker::Client`): This provides a new public API
  (`otHistoryTrackerQueryNetInfo`) to send a query to a remote
  device. It handles sending the request and processing the received
  answer(s), passing the retrieved history entries to the user via a
  callback. A function to cancel an ongoing query
  (`otHistoryTrackerCancelQuery`) is also added.

- TLVs (`history_tracker_tlvs`): New TLVs are defined for the
  query/answer protocol, including `RequestTlv` to specify the query
  parameters, `AnswerTlv` to manage multi-message responses,
  `NetworkInfoTlv` to carry the data, and `QueryIdTlv` to correlate
  requests and responses.

A new CLI command, `history query netinfo`, is added to use the new
client API. The existing `history netinfo` output logic is refactored
into helper methods to be shared by both the local and remote history
commands.

The new feature can be enabled/disabled using two new configuration
flags:
- `OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE`
- `OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE`
This commit is contained in:
Abtin Keshavarzian
2025-12-09 10:34:06 -08:00
committed by GitHub
parent a3b8361a19
commit 0c592029ba
25 changed files with 1599 additions and 23 deletions
+64
View File
@@ -31,6 +31,7 @@
#include <stdint.h>
#include <openthread/border_routing.h>
#include <openthread/error.h>
#include <openthread/instance.h>
#include <openthread/ip6.h>
#include <openthread/message.h>
@@ -651,6 +652,69 @@ const otHistoryTrackerAilRouter *otHistoryTrackerIterateAilRoutersHistory(otInst
*/
void otHistoryTrackerEntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize);
//----------------------------------------------------------------------------------------------------------------------
// History Tracker Client function (requires `OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE`)
/**
* Callback function pointer type to report the retrieved Network Info entries from a query to another device.
*
* Used when `OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE` is enabled.
*
* @param[in] aError Indicates the status of the query and entries being reported:
* - `OT_ERROR_PENDING`: There are more entries to be reported.
* - `OT_ERROR_NONE`: This is the last entry, and the query is complete.
* - `OT_ERROR_RESPONSE_TIMEOUT`: Timed out waiting for a response.
* - `OT_ERROR_PARSE`: The received query answer does not follow the expected format.
* @param[in] aNetworkInfo The network information entry. This may be `NULL` if `aError` is `OT_ERROR_NONE`
* (indicating the end of the list) or on certain error conditions.
* @param[in] aEntryAge The entry age in milliseconds. Applicable only when @p aNetworkInfo is not `NULL`.
* @param[in] aContext An arbitrary callback context provided by the caller during the query.
*/
typedef void (*otHistoryTrackerNetInfoCallback)(otError aError,
const otHistoryTrackerNetworkInfo *aNetworkInfo,
uint32_t aEntryAge,
void *aContext);
/**
* Queries for Network Info history entries from a specified RLOC16.
*
* Requires `OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE`.
*
* Upon successful initiation of the query, the provided @p aCallback will be invoked to report the requested entries.
*
* The callback parameter `aError` indicates if any error occurs. If there are more entries to be provided, `aError`
* will be set to `OT_ERROR_PENDING`. The end of the list is indicated by `aError` being set to `OT_ERROR_NONE` with a
* null entry pointer. Any other errors, such as `OT_ERROR_RESPONSE_TIMEOUT` or `OT_ERROR_PARSE` (if the received
* response has an invalid format), will also be indicated by `aError`.
*
* @param[in] aInstance The OpenThread instance.
* @param[in] aRloc16 The RLOC16 of the device to query.
* @param[in] aMaxEntries The maximum number of entries to request (0 indicates all available entries).
* @param[in] aMaxEntryAge The maximum age (in milliseconds) of entries to request (0 indicates no age limit).
* @param[in] aCallback A pointer to a callback function to be called when the query response is received.
* @param[in] aContext A user-defined context pointer to be passed to the callback function.
*
* @retval OT_ERROR_NONE If the query was successfully sent.
* @retval OT_ERROR_BUSY If a query is already in progress.
* @retval OT_ERROR_NO_BUFS If there are insufficient message buffers to send the query.
* @retval OT_ERROR_INVALID_STATE If device is not attached.
*/
otError otHistoryTrackerQueryNetInfo(otInstance *aInstance,
uint16_t aRloc16,
uint16_t aMaxEntries,
uint32_t aMaxEntryAge,
otHistoryTrackerNetInfoCallback aCallback,
void *aContext);
/**
* Cancels any ongoing query.
*
* Requires `OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE`.
*
* @param[in] aInstance The OpenThread instance.
*/
void otHistoryTrackerCancelQuery(otInstance *aInstance);
/**
* @}
*/
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (557)
#define OPENTHREAD_API_VERSION (558)
/**
* @addtogroup api-instance
+1
View File
@@ -111,6 +111,7 @@ class Interpreter : public OutputImplementer, public Utils
friend class Commissioner;
friend class Dns;
friend class Joiner;
friend class History;
friend class LinkMetrics;
friend class Mdns;
friend class MeshDiag;
+152 -17
View File
@@ -647,20 +647,12 @@ template <> otError History::Process<Cmd("netinfo")>(Arg aArgs[])
otHistoryTrackerIterator iterator;
const otHistoryTrackerNetworkInfo *info;
uint32_t entryAge;
char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
char linkModeString[Interpreter::kLinkModeStringSize];
SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
if (!isList)
{
// | Age | Role | Mode | RLOC16 | Partition ID |
// +----------------------+----------+------+--------+--------------+
static const char *const kNetInfoTitles[] = {"Age", "Role", "Mode", "RLOC16", "Partition ID"};
static const uint8_t kNetInfoColumnWidths[] = {22, 10, 6, 8, 14};
OutputTableHeader(kNetInfoTitles, kNetInfoColumnWidths);
OutputNetInfoTableHeader();
}
otHistoryTrackerInitIterator(&iterator);
@@ -670,18 +662,37 @@ template <> otError History::Process<Cmd("netinfo")>(Arg aArgs[])
info = otHistoryTrackerIterateNetInfoHistory(GetInstancePtr(), &iterator, &entryAge);
VerifyOrExit(info != nullptr);
otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
OutputLine(
isList ? "%s -> role:%s mode:%s rloc16:0x%04x partition-id:%lu" : "| %20s | %-8s | %-4s | 0x%04x | %12lu |",
ageString, otThreadDeviceRoleToString(info->mRole),
Interpreter::LinkModeToString(info->mMode, linkModeString), info->mRloc16, ToUlong(info->mPartitionId));
OutputNetInfoEntry(isList, *info, entryAge);
}
exit:
return error;
}
void History::OutputNetInfoTableHeader(void)
{
// | Age | Role | Mode | RLOC16 | Partition ID |
// +----------------------+----------+------+--------+--------------+
static const char *const kNetInfoTitles[] = {"Age", "Role", "Mode", "RLOC16", "Partition ID"};
static const uint8_t kNetInfoColumnWidths[] = {22, 10, 6, 8, 14};
OutputTableHeader(kNetInfoTitles, kNetInfoColumnWidths);
}
void History::OutputNetInfoEntry(bool aIsList, const otHistoryTrackerNetworkInfo &aInfo, uint32_t aEntryAge)
{
char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
char linkModeString[Interpreter::kLinkModeStringSize];
otHistoryTrackerEntryAgeToString(aEntryAge, ageString, sizeof(ageString));
OutputLine(aIsList ? "%s -> role:%s mode:%s rloc16:0x%04x partition-id:%lu"
: "| %20s | %-8s | %-4s | 0x%04x | %12lu |",
ageString, otThreadDeviceRoleToString(aInfo.mRole),
Interpreter::LinkModeToString(aInfo.mMode, linkModeString), aInfo.mRloc16, ToUlong(aInfo.mPartitionId));
}
/**
* @cli history rx
* @code
@@ -1899,6 +1910,126 @@ const char *History::DnsSrpAddrTypeToString(otHistoryTrackerDnsSrpAddrType aType
return Stringify(aType, kAddrTypeStrings, "--");
}
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
otError History::ParseQueryArgs(Arg aArgs[],
bool &aIsList,
uint16_t &aRloc16,
uint16_t &aNumEntries,
uint32_t &aMaxEntryAge) const
{
otError error = OT_ERROR_NONE;
if (*aArgs == "list")
{
aArgs++;
aIsList = true;
}
else
{
aIsList = false;
}
SuccessOrExit(error = aArgs->ParseAsUint16(aRloc16));
aArgs++;
if (aArgs->ParseAsUint16(aNumEntries) == OT_ERROR_NONE)
{
aArgs++;
}
else
{
aNumEntries = 0;
}
if (aArgs->ParseAsUint32(aMaxEntryAge) == OT_ERROR_NONE)
{
aArgs++;
}
else
{
aMaxEntryAge = 0;
}
error = aArgs[0].IsEmpty() ? OT_ERROR_NONE : OT_ERROR_INVALID_ARGS;
exit:
return error;
}
template <> otError History::Process<Cmd("query")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
uint16_t rloc16;
uint16_t maxEntries;
uint32_t maxEntryAge;
/**
* @cli history query netinfo
* @code
* history query netinfo 0xac00
* | Age | Role | Mode | RLOC16 | Partition ID |
* +----------------------+----------+------+--------+--------------+
* | 00:00:36.786 | detached | rdn | 0xac00 | 807291876 |
* | 00:00:43.966 | detached | rdn | 0xfffe | 0 |
* Done
* @endcode
* @cparam history query netinfo [@ca{list}] @ca{rloc16} [@ca{num-entries}] [@ca{max-entry-age}]
* * Use the `list` option to display the output in list format. Otherwise, the output is shown in table format.
* * The `rloc16` indicates the RLOC16 of the device to query.
* * Use the `num-entries` option to limit the output to the number of most-recent entries specified. If this option
* is not used or set to zero, all stored entries are shown in the output.
* * Use the `max-entry-age` option to limit maximum age of entries. If this option is not used or set to zero, all
* stored entries are shown in the output.
* @par
* Queries the "netinfo" history entries from another device and outputs them in a table or list format. For details
* on the table format and its entries, please refer to the `history netinfo` command.
* @sa otHistoryTrackerQueryNetInfo
*/
if (aArgs[0] == "netinfo")
{
SuccessOrExit(error = ParseQueryArgs(&aArgs[1], mQueryUseListFormat, rloc16, maxEntries, maxEntryAge));
SuccessOrExit(error = otHistoryTrackerQueryNetInfo(GetInstancePtr(), rloc16, maxEntries, maxEntryAge,
HandleNetInfo, this));
if (!mQueryUseListFormat)
{
OutputNetInfoTableHeader();
}
error = OT_ERROR_PENDING;
}
else
{
error = OT_ERROR_INVALID_ARGS;
}
exit:
return error;
}
void History::HandleNetInfo(otError aError,
const otHistoryTrackerNetworkInfo *aNetworkInfo,
uint32_t aEntryAge,
void *aContext)
{
static_cast<History *>(aContext)->HandleNetInfo(aError, aNetworkInfo, aEntryAge);
}
void History::HandleNetInfo(otError aError, const otHistoryTrackerNetworkInfo *aNetworkInfo, uint32_t aEntryAge)
{
if (aNetworkInfo != nullptr)
{
OutputNetInfoEntry(mQueryUseListFormat, *aNetworkInfo, aEntryAge);
}
OutputResult(aError);
}
void History::OutputResult(otError aError) { Interpreter::GetInterpreter().OutputResult(aError); }
#endif // #if OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
otError History::Process(Arg aArgs[])
{
#define CmdEntry(aCommandString) {aCommandString, &History::Process<Cmd(aCommandString)>}
@@ -1915,8 +2046,12 @@ otError History::Process(Arg aArgs[])
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
CmdEntry("omrprefix"), CmdEntry("onlinkprefix"),
#endif
CmdEntry("prefix"), CmdEntry("route"), CmdEntry("router"),
CmdEntry("rx"), CmdEntry("rxtx"), CmdEntry("tx"),
CmdEntry("prefix"),
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
CmdEntry("query"),
#endif
CmdEntry("route"), CmdEntry("router"), CmdEntry("rx"),
CmdEntry("rxtx"), CmdEntry("tx"),
};
#undef CmdEntry
+24
View File
@@ -97,11 +97,35 @@ private:
void OutputRxTxEntryListFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx);
void OutputRxTxEntryTableFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx);
void OutputNetInfoTableHeader(void);
void OutputNetInfoEntry(bool aIsList, const otHistoryTrackerNetworkInfo &aInfo, uint32_t aEntryAge);
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
void OutputResult(otError aError);
otError ParseQueryArgs(Arg aArgs[],
bool &aIsList,
uint16_t &aRloc16,
uint16_t &aNumEntries,
uint32_t &aMaxEntryAge) const;
void HandleNetInfo(otError aError, const otHistoryTrackerNetworkInfo *aNetworkInfo, uint32_t aEntryAge);
static void HandleNetInfo(otError aError,
const otHistoryTrackerNetworkInfo *aNetworkInfo,
uint32_t aEntryAge,
void *aContext);
#endif
static const char *MessagePriorityToString(uint8_t aPriority);
static const char *RadioTypeToString(const otHistoryTrackerMessageInfo &aInfo);
static const char *MessageTypeToString(const otHistoryTrackerMessageInfo &aInfo);
static const char *DnsSrpAddrTypeToString(otHistoryTrackerDnsSrpAddrType aType);
static const char *AilRouterEventToString(otHistoryTrackerAilRouterEvent aEvent);
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
bool mQueryUseListFormat;
#endif
};
} // namespace Cli
+6
View File
@@ -621,6 +621,12 @@ openthread_core_files = [
"utils/heap.hpp",
"utils/history_tracker.cpp",
"utils/history_tracker.hpp",
"utils/history_tracker_client.cpp",
"utils/history_tracker_client.hpp",
"utils/history_tracker_server.cpp",
"utils/history_tracker_server.hpp",
"utils/history_tracker_tlvs.cpp",
"utils/history_tracker_tlvs.hpp",
"utils/jam_detector.cpp",
"utils/jam_detector.hpp",
"utils/link_metrics_manager.cpp",
+3
View File
@@ -280,6 +280,9 @@ set(COMMON_SOURCES
utils/flash.cpp
utils/heap.cpp
utils/history_tracker.cpp
utils/history_tracker_client.cpp
utils/history_tracker_server.cpp
utils/history_tracker_tlvs.cpp
utils/jam_detector.cpp
utils/link_metrics_manager.cpp
utils/mesh_diag.cpp
+20
View File
@@ -204,4 +204,24 @@ void otHistoryTrackerEntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_
HistoryTracker::Local::EntryAgeToString(aEntryAge, aBuffer, aSize);
}
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
otError otHistoryTrackerQueryNetInfo(otInstance *aInstance,
uint16_t aRloc16,
uint16_t aMaxEntries,
uint32_t aMaxEntryAge,
otHistoryTrackerNetInfoCallback aCallback,
void *aContext)
{
return AsCoreType(aInstance).Get<HistoryTracker::Client>().QueryNetInfo(aRloc16, aMaxEntries, aMaxEntryAge,
aCallback, aContext);
}
void otHistoryTrackerCancelQuery(otInstance *aInstance)
{
return AsCoreType(aInstance).Get<HistoryTracker::Client>().CancelQuery();
}
#endif
#endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
+19
View File
@@ -52,6 +52,25 @@
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE 0
#endif
/**
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE
*
* Define as 1 to enable History Tracker Server module (answering received TMF history queries).
*/
#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
#endif
/**
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
*
* Define as 1 to enable History Tracker Client module (using TMF history query to retrieve history info from other
* devices).
*/
#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
#endif
/**
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_INFO_LIST_SIZE
*
+6
View File
@@ -260,6 +260,12 @@ Instance::Instance(void)
#endif
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
, mHistoryTrackerLocal(*this)
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE
, mHistoryTrackerServer(*this)
#endif
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
, mHistoryTrackerClient(*this)
#endif
#endif
#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE
, mLinkMetricsManager(*this)
+17
View File
@@ -147,6 +147,8 @@
#include "utils/channel_monitor.hpp"
#include "utils/heap.hpp"
#include "utils/history_tracker.hpp"
#include "utils/history_tracker_client.hpp"
#include "utils/history_tracker_server.hpp"
#include "utils/jam_detector.hpp"
#include "utils/link_metrics_manager.hpp"
#include "utils/mesh_diag.hpp"
@@ -706,6 +708,12 @@ private:
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
HistoryTracker::Local mHistoryTrackerLocal;
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE
HistoryTracker::Server mHistoryTrackerServer;
#endif
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
HistoryTracker::Client mHistoryTrackerClient;
#endif
#endif
#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE
@@ -1030,7 +1038,16 @@ template <> inline Utils::MeshDiag &Instance::Get(void) { return mMeshDiag; }
#endif
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
template <> inline HistoryTracker::Local &Instance::Get(void) { return mHistoryTrackerLocal; }
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE
template <> inline HistoryTracker::Server &Instance::Get(void) { return mHistoryTrackerServer; }
#endif
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
template <> inline HistoryTracker::Client &Instance::Get(void) { return mHistoryTrackerClient; }
#endif
#endif
#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE
+7
View File
@@ -172,6 +172,13 @@ bool Agent::HandleResource(const char *aUriPath, Message &aMessage, const Ip6::M
Case(kUriDiagnosticGetAnswer, NetworkDiagnostic::Client);
#endif
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE && OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE
Case(kUriHistoryQuery, HistoryTracker::Server);
#endif
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE && OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
Case(kUriHistoryAnswer, HistoryTracker::Client);
#endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
#if OPENTHREAD_CONFIG_BACKBONE_ROUTER_MULTICAST_ROUTING_ENABLE
Case(kUriMlr, BackboneRouter::Manager);
+6
View File
@@ -91,6 +91,8 @@ static constexpr Entry kEntries[] = {
{"d/dg"}, // kUriDiagnosticGetRequest
{"d/dq"}, // kUriDiagnosticGetQuery
{"d/dr"}, // kUriDiagnosticReset
{"h/an"}, // kUriHistoryAnswer
{"h/qy"}, // kUriHistoryQuery
{"n/dn"}, // kUriDuaRegistrationNotify
{"n/dr"}, // kUriDuaRegistrationRequest
{"n/mr"}, // kUriMlr
@@ -137,6 +139,8 @@ static_assert(AreConstStringsEqual(kEntries[kUriDiagnosticGetAnswer].mPath, "d/d
static_assert(AreConstStringsEqual(kEntries[kUriDiagnosticGetRequest].mPath, "d/dg"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDiagnosticGetQuery].mPath, "d/dq"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDiagnosticReset].mPath, "d/dr"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriHistoryAnswer].mPath, "h/an"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriHistoryQuery].mPath, "h/qy"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDuaRegistrationNotify].mPath, "n/dn"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriDuaRegistrationRequest].mPath, "n/dr"), "kEntries is invalid");
static_assert(AreConstStringsEqual(kEntries[kUriMlr].mPath, "n/mr"), "kEntries is invalid");
@@ -200,6 +204,8 @@ template <> const char *UriToString<kUriDiagnosticGetAnswer>(void) { return "Dia
template <> const char *UriToString<kUriDiagnosticGetRequest>(void) { return "DiagGetRequest"; }
template <> const char *UriToString<kUriDiagnosticGetQuery>(void) { return "DiagGetQuery"; }
template <> const char *UriToString<kUriDiagnosticReset>(void) { return "DiagReset"; }
template <> const char *UriToString<kUriHistoryAnswer>(void) { return "HistAnswer"; }
template <> const char *UriToString<kUriHistoryQuery>(void) { return "HistQuery"; }
template <> const char *UriToString<kUriDuaRegistrationNotify>(void) { return "DuaRegNotify"; }
template <> const char *UriToString<kUriDuaRegistrationRequest>(void) { return "DuaRegRequest"; }
template <> const char *UriToString<kUriMlr>(void) { return "Mlr"; }
+2
View File
@@ -83,6 +83,8 @@ enum Uri : uint8_t
kUriDiagnosticGetRequest, ///< Network Diagnostic Get Request ("d/dg")
kUriDiagnosticGetQuery, ///< Network Diagnostic Get Query ("d/dq")
kUriDiagnosticReset, ///< Network Diagnostic Reset ("d/dr")
kUriHistoryAnswer, ///< History Answer ("h/an")
kUriHistoryQuery, ///< History Query ("h/qy")
kUriDuaRegistrationNotify, ///< DUA Registration Notification ("n/dn")
kUriDuaRegistrationRequest, ///< DUA Registration Request ("n/dr")
kUriMlr, ///< Multicast Listener Registration ("n/mr")
+9 -2
View File
@@ -89,14 +89,21 @@ public:
* An iterator MUST be initialized before it is used. An iterator can be initialized again to start from
* the beginning of the list.
*/
void Init(void) { ResetEntryNumber(), SetInitTime(); }
void Init(void) { Init(TimerMilli::GetNow()); }
/**
* Initializes an `Iterator`
*
* @param[in] aNow The now time.
*/
void Init(TimeMilli aNow) { ResetEntryNumber(), SetInitTime(aNow); }
private:
uint16_t GetEntryNumber(void) const { return mData16; }
void ResetEntryNumber(void) { mData16 = 0; }
void IncrementEntryNumber(void) { mData16++; }
TimeMilli GetInitTime(void) const { return TimeMilli(mData32); }
void SetInitTime(void) { mData32 = TimerMilli::GetNow().GetValue(); }
void SetInitTime(TimeMilli aNow) { mData32 = aNow.GetValue(); }
};
typedef otHistoryTrackerNetworkInfo NetworkInfo; ///< Thread network info.
+232
View File
@@ -0,0 +1,232 @@
/*
* Copyright (c) 2025, 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 History Tracker Client.
*/
#include "history_tracker_client.hpp"
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE && OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
#include "instance/instance.hpp"
namespace ot {
namespace HistoryTracker {
RegisterLogModule("HistoryClient");
Client::Client(Instance &aInstance)
: InstanceLocator(aInstance)
, mActive(false)
, mQueryRloc16(0)
, mQueryId(0)
, mAnswerIndex(0)
, mTimer(aInstance)
{
}
void Client::CancelQuery(void)
{
mActive = false;
mTimer.Stop();
}
Error Client::QueryNetInfo(uint16_t aRloc16,
uint16_t aMaxEntries,
uint32_t aMaxEntryAge,
NetInfoCallback aCallback,
void *aContext)
{
Error error = kErrorNone;
VerifyOrExit(!mActive, error = kErrorBusy);
mCallbacks.mNetInfo.Set(aCallback, aContext);
error = SendQuery(Tlv::kNetworkInfo, aMaxEntries, aMaxEntryAge, aRloc16);
exit:
return error;
}
Error Client::SendQuery(Tlv::Type aTlvType, uint16_t aMaxEntries, uint32_t aMaxEntryAge, uint16_t aRloc16)
{
Error error = kErrorNone;
OwnedPtr<Coap::Message> message;
Tmf::MessageInfo messageInfo(GetInstance());
RequestTlv requestTlv;
VerifyOrExit(Get<Mle::Mle>().IsAttached(), error = kErrorInvalidState);
message.Reset(Get<Tmf::Agent>().NewNonConfirmablePostMessage(kUriHistoryQuery));
VerifyOrExit(message != nullptr, error = kErrorNoBufs);
IgnoreError(message->SetPriority(Message::kPriorityLow));
mQueryId++;
SuccessOrExit(error = Tlv::Append<QueryIdTlv>(*message, mQueryId));
requestTlv.Init(aTlvType, aMaxEntries, aMaxEntryAge);
SuccessOrExit(error = message->Append(requestTlv));
messageInfo.SetSockAddrToRloc();
messageInfo.GetPeerAddr().SetToRoutingLocator(Get<Mle::Mle>().GetMeshLocalPrefix(), aRloc16);
SuccessOrExit(error = Get<Tmf::Agent>().SendMessage(*message, messageInfo));
message.Release();
LogInfo("Sent %s for TLV %u to 0x%04x", UriToString<kUriHistoryQuery>(), aTlvType, aRloc16);
mActive = true;
mTlvType = aTlvType;
mQueryRloc16 = aRloc16;
mAnswerIndex = 0;
mTimer.Start(kResponseTimeout);
exit:
return error;
}
template <> void Client::HandleTmf<kUriHistoryAnswer>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
VerifyOrExit(aMessage.IsConfirmablePostRequest());
IgnoreError(Get<Tmf::Agent>().SendEmptyAck(aMessage, aMessageInfo));
LogInfo("Received %s from %s", ot::UriToString<kUriHistoryAnswer>(),
aMessageInfo.GetPeerAddr().ToString().AsCString());
SuccessOrExit(ProcessAnswer(aMessage, aMessageInfo));
switch (mTlvType)
{
case Tlv::kNetworkInfo:
ProcessNetInfoAnswer(aMessage);
break;
default:
ExitNow();
}
exit:
return;
}
Error Client::ProcessAnswer(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
Error error = kErrorFailed;
AnswerTlv answerTlv;
uint16_t queryId;
VerifyOrExit(mActive);
VerifyOrExit(Get<Mle::Mle>().IsRoutingLocator(aMessageInfo.GetPeerAddr()));
VerifyOrExit(aMessageInfo.GetPeerAddr().GetIid().GetLocator() == mQueryRloc16);
SuccessOrExit(Tlv::Find<QueryIdTlv>(aMessage, queryId));
VerifyOrExit(queryId == mQueryId);
SuccessOrExit(Tlv::FindTlv(aMessage, answerTlv));
if (answerTlv.GetIndex() != mAnswerIndex)
{
Finalize(kErrorResponseTimeout);
ExitNow();
}
mAnswerIndex++;
error = kErrorNone;
exit:
return error;
}
void Client::ProcessNetInfoAnswer(const Coap::Message &aMessage)
{
Error error = kErrorNone;
OffsetRange offsetRange;
Tlv tlv;
NetworkInfoTlv netInfoTlv;
NetworkInfo netInfo;
offsetRange.InitFromMessageOffsetToEnd(aMessage);
for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(tlv.GetSize()))
{
SuccessOrExit(error = aMessage.Read(offsetRange, tlv));
VerifyOrExit(offsetRange.Contains(tlv.GetSize()), error = kErrorParse);
if (tlv.GetType() != Tlv::kNetworkInfo)
{
continue;
}
if (tlv.GetLength() == 0)
{
Finalize(kErrorNone);
ExitNow();
}
SuccessOrExit(error = aMessage.Read(offsetRange, netInfoTlv));
VerifyOrExit(netInfoTlv.IsValid(), error = kErrorParse);
netInfoTlv.CopyTo(netInfo);
mCallbacks.mNetInfo.InvokeIfSet(kErrorPending, &netInfo, netInfoTlv.GetEntryAge());
VerifyOrExit(mActive);
}
exit:
if (error != kErrorNone)
{
Finalize(error);
}
}
void Client::Finalize(Error aError)
{
VerifyOrExit(mActive);
CancelQuery();
switch (mTlvType)
{
case Tlv::kNetworkInfo:
mCallbacks.mNetInfo.InvokeIfSet(aError, nullptr, 0);
break;
default:
break;
}
exit:
return;
}
void Client::HandleTimer(void) { Finalize(kErrorResponseTimeout); }
} // namespace HistoryTracker
} // namespace ot
#endif // #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE && OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
+137
View File
@@ -0,0 +1,137 @@
/*
* Copyright (c) 2025, 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 to support History Tracker Client.
*/
#ifndef HISTORY_TRACKER_CLIENT_HPP_
#define HISTORY_TRACKER_CLIENT_HPP_
#include "openthread-core-config.h"
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE && OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
#include <openthread/history_tracker.h>
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
#include "common/timer.hpp"
#include "thread/tmf.hpp"
#include "utils/history_tracker.hpp"
#include "utils/history_tracker_tlvs.hpp"
namespace ot {
namespace HistoryTracker {
/**
* Implements a History Tracker Client
*/
class Client : public InstanceLocator
{
friend class Tmf::Agent;
public:
typedef otHistoryTrackerNetInfoCallback NetInfoCallback; ///< Callback function type for Network Info queries.
/**
* Constructor for the Client.
*
* @param[in] aInstance The OpenThread instance.
*/
explicit Client(Instance &aInstance);
/**
* Cancels any ongoing query.
*/
void CancelQuery(void);
/**
* Queries for Network Info entries from a specified RLOC16.
*
* Upon successful initiation of the query, the provided @p aCallback will be invoked to report the requested
* retrieved entries (parsing the answer). The callback parameter `aError` indicates if any error occurs. If there
* are more entries to be provided, `aError` will be set to `kErrorPending`. The end of the list is indicated by
* `aError` being set to `kErrorNone` with a null entry pointer. Any other errors, such as `kErrorResponseTimeout`
* or `kErrorParse` (if the received response has an invalid format), will also be indicated by `aError`.
*
* @param[in] aRloc16 The RLOC16 of the device to query.
* @param[in] aMaxEntries The maximum number of entries to request (0 indicates all available entries).
* @param[in] aMaxEntryAge The maximum age (in milliseconds) of entries to request (0 indicates no age limit).
* @param[in] aCallback A pointer to a callback function to be called when the query response is received.
* @param[in] aContext A user-defined context pointer to be passed to the callback function.
*
* @retval kErrorNone If the query was successfully sent.
* @retval kErrorBusy If a query is already in progress.
* @retval kErrorNoBufs If there are insufficient message buffers to send the query.
* @retval kErrorInvalidState Device is not attached.
*/
Error QueryNetInfo(uint16_t aRloc16,
uint16_t aMaxEntries,
uint32_t aMaxEntryAge,
NetInfoCallback aCallback,
void *aContext);
private:
static constexpr uint16_t kResponseTimeout = 5000;
Error SendQuery(Tlv::Type aTlvType, uint16_t aMaxEntries, uint32_t aMaxEntryAge, uint16_t aRloc16);
Error ProcessAnswer(const Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
void ProcessNetInfoAnswer(const Coap::Message &aMessage);
void Finalize(Error aError);
void HandleTimer(void);
template <Uri kUri> void HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
union Callbacks
{
Callbacks(void) {}
Callback<NetInfoCallback> mNetInfo;
};
using TimeoutTimer = TimerMilliIn<Client, &Client::HandleTimer>;
bool mActive;
Tlv::Type mTlvType;
Callbacks mCallbacks;
uint16_t mQueryRloc16;
uint16_t mQueryId;
uint16_t mAnswerIndex;
TimeoutTimer mTimer;
};
DeclareTmfHandler(Client, kUriHistoryAnswer);
} // namespace HistoryTracker
} // namespace ot
#endif // #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE && OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE
#endif // HISTORY_TRACKER_CLIENT_HPP_
+343
View File
@@ -0,0 +1,343 @@
/*
* Copyright (c) 2025, 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 History Tracker Server.
*/
#include "history_tracker_server.hpp"
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE && OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE
#include "instance/instance.hpp"
namespace ot {
namespace HistoryTracker {
RegisterLogModule("HistoryServer");
Server::Server(Instance &aInstance)
: InstanceLocator(aInstance)
{
}
template <> void Server::HandleTmf<kUriHistoryQuery>(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo)
{
VerifyOrExit(aMessage.IsPostRequest());
LogInfo("Received %s from %s", UriToString<kUriHistoryQuery>(), aMessageInfo.GetPeerAddr().ToString().AsCString());
if (aMessage.IsConfirmable())
{
IgnoreError(Get<Tmf::Agent>().SendEmptyAck(aMessage, aMessageInfo));
}
PrepareAndSendAnswers(aMessageInfo.GetPeerAddr(), aMessage);
exit:
return;
}
Error Server::AllocateAnswer(Coap::Message *&aAnswer, AnswerInfo &aInfo)
{
// Allocates an `Answer` message, adds it to `mAnswerQueue`,
// updates the `aInfo.mFirstAnswer` if it is the first allocated
// messages, and appends `QueryIdTlv` to the message (if needed).
Error error = kErrorNone;
aAnswer = Get<Tmf::Agent>().NewConfirmablePostMessage(kUriHistoryAnswer);
VerifyOrExit(aAnswer != nullptr, error = kErrorNoBufs);
IgnoreError(aAnswer->SetPriority(aInfo.mPriority));
mAnswerQueue.Enqueue(*aAnswer);
if (aInfo.mFirstAnswer == nullptr)
{
aInfo.mFirstAnswer = aAnswer;
}
if (aInfo.mHasQueryId)
{
SuccessOrExit(error = Tlv::Append<QueryIdTlv>(*aAnswer, aInfo.mQueryId));
}
exit:
return error;
}
bool Server::IsLastAnswer(const Coap::Message &aAnswer) const
{
// Indicates whether `aAnswer` is the last one associated with
// the same query.
bool isLast = true;
AnswerTlv answerTlv;
// If there is no Answer TLV, we assume it is the last answer.
SuccessOrExit(Tlv::FindTlv(aAnswer, answerTlv));
isLast = answerTlv.IsLast();
exit:
return isLast;
}
void Server::FreeAllRelatedAnswers(Coap::Message &aFirstAnswer)
{
// Dequeues and frees all answer messages related to the same query
// as `aFirstAnswer`. Note that related answers are enqueued in
// order.
Coap::Message *answer = &aFirstAnswer;
while (answer != nullptr)
{
Coap::Message *next = IsLastAnswer(*answer) ? nullptr : answer->GetNextCoapMessage();
mAnswerQueue.DequeueAndFree(*answer);
answer = next;
}
}
void Server::PrepareAndSendAnswers(const Ip6::Address &aDestination, const Message &aRequest)
{
Coap::Message *answer;
Error error;
AnswerInfo info;
OffsetRange offsetRange;
Tlv tlv;
RequestTlv requestTlv;
AnswerTlv answerTlv;
if (Tlv::Find<QueryIdTlv>(aRequest, info.mQueryId) == kErrorNone)
{
info.mHasQueryId = true;
}
info.mPriority = aRequest.GetPriority();
SuccessOrExit(error = AllocateAnswer(answer, info));
offsetRange.InitFromMessageOffsetToEnd(aRequest);
for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(tlv.GetSize()))
{
SuccessOrExit(error = aRequest.Read(offsetRange, tlv));
VerifyOrExit(offsetRange.Contains(tlv.GetSize()), error = kErrorParse);
if (tlv.GetType() == Tlv::kRequest)
{
SuccessOrExit(error = aRequest.Read(offsetRange, requestTlv));
VerifyOrExit(requestTlv.IsValid(), error = kErrorParse);
switch (requestTlv.GetTlvType())
{
case Tlv::kNetworkInfo:
SuccessOrExit(error = AppendNetworkInfo(answer, info, requestTlv));
break;
default:
break;
}
SuccessOrExit(error = CheckAnswerLength(answer, info));
}
}
answerTlv.Init(info.mAnswerIndex, /* aIsLast */ true);
SuccessOrExit(error = answer->Append(answerTlv));
SendNextAnswer(*info.mFirstAnswer, aDestination);
exit:
if ((error != kErrorNone) && (info.mFirstAnswer != nullptr))
{
FreeAllRelatedAnswers(*info.mFirstAnswer);
}
}
Error Server::CheckAnswerLength(Coap::Message *&aAnswer, AnswerInfo &aInfo)
{
// Checks the length of the `aAnswer` message and if it is above
// the threshold, it enqueues the message for transmission after
// appending an Answer TLV with the current index to the message.
// In this case, it will also allocate a new answer message.
Error error = kErrorNone;
AnswerTlv answerTlv;
VerifyOrExit(aAnswer->GetLength() >= kAnswerMessageLengthThreshold);
answerTlv.Init(aInfo.mAnswerIndex++, /* aIsLast */ false);
SuccessOrExit(error = aAnswer->Append(answerTlv));
error = AllocateAnswer(aAnswer, aInfo);
exit:
return error;
}
void Server::SendNextAnswer(Coap::Message &aAnswer, const Ip6::Address &aDestination)
{
Error error = kErrorNone;
Coap::Message *nextAnswer = IsLastAnswer(aAnswer) ? nullptr : aAnswer.GetNextCoapMessage();
Tmf::MessageInfo messageInfo(GetInstance());
mAnswerQueue.Dequeue(aAnswer);
PrepareMessageInfoForDest(aDestination, messageInfo);
// When sending the message, we pass `nextAnswer` as `aContext`
// to be used when invoking callback `HandleAnswerResponse()`.
error = Get<Tmf::Agent>().SendMessage(aAnswer, messageInfo, HandleAnswerResponse, nextAnswer);
if (error != kErrorNone)
{
// If the `SendMessage()` fails, we `Free` the dequeued
// `aAnswer` and all the related next answers in the queue.
aAnswer.Free();
if (nextAnswer != nullptr)
{
FreeAllRelatedAnswers(*nextAnswer);
}
}
}
void Server::PrepareMessageInfoForDest(const Ip6::Address &aDestination, Tmf::MessageInfo &aMessageInfo) const
{
if (aDestination.IsMulticast())
{
aMessageInfo.SetMulticastLoop(true);
}
if (aDestination.IsLinkLocalUnicastOrMulticast())
{
aMessageInfo.SetSockAddr(Get<Mle::Mle>().GetLinkLocalAddress());
}
else
{
aMessageInfo.SetSockAddrToRloc();
}
aMessageInfo.SetPeerAddr(aDestination);
}
void Server::HandleAnswerResponse(void *aContext,
otMessage *aMessage,
const otMessageInfo *aMessageInfo,
otError aResult)
{
Coap::Message *nextAnswer = static_cast<Coap::Message *>(aContext);
VerifyOrExit(nextAnswer != nullptr);
nextAnswer->Get<Server>().HandleAnswerResponse(*nextAnswer, AsCoapMessagePtr(aMessage), AsCoreTypePtr(aMessageInfo),
aResult);
exit:
return;
}
void Server::HandleAnswerResponse(Coap::Message &aNextAnswer,
Coap::Message *aResponse,
const Ip6::MessageInfo *aMessageInfo,
Error aResult)
{
Error error = aResult;
SuccessOrExit(error);
VerifyOrExit(aResponse != nullptr && aMessageInfo != nullptr, error = kErrorDrop);
VerifyOrExit(aResponse->GetCode() == Coap::kCodeChanged, error = kErrorDrop);
SendNextAnswer(aNextAnswer, aMessageInfo->GetPeerAddr());
exit:
if (error != kErrorNone)
{
FreeAllRelatedAnswers(aNextAnswer);
}
}
Error Server::AppendNetworkInfo(Coap::Message *&aAnswer, AnswerInfo &aInfo, const RequestTlv &aRequestTlv)
{
Error error = kErrorNone;
Iterator iterator;
uint32_t maxEntryAge = aRequestTlv.GetMaxEntryAge();
uint16_t maxCount = aRequestTlv.GetNumEntries();
iterator.Init(aInfo.mNow);
for (uint16_t count = 0; (maxCount == 0) || (count < maxCount); count++)
{
const NetworkInfo *networkInfo;
uint32_t entryAge;
NetworkInfoTlv networkInfoTlv;
networkInfo = Get<Local>().IterateNetInfoHistory(iterator, entryAge);
if (networkInfo == nullptr)
{
break;
}
if ((maxEntryAge != 0) && (entryAge > maxEntryAge))
{
break;
}
networkInfoTlv.InitFrom(*networkInfo, entryAge);
SuccessOrExit(error = aAnswer->Append(networkInfoTlv));
SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo));
}
SuccessOrExit(error = AppendEmptyTlv(*aAnswer, Tlv::kNetworkInfo));
exit:
return error;
}
Error Server::AppendEmptyTlv(Coap::Message &aAnswer, Tlv::Type aTlvType)
{
Tlv tlv;
tlv.SetType(aTlvType);
tlv.SetLength(0);
return aAnswer.Append(tlv);
}
} // namespace HistoryTracker
} // namespace ot
#endif // #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE && OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE
+116
View File
@@ -0,0 +1,116 @@
/*
* Copyright (c) 2025, 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 to support History Tracker Server (TMF).
*/
#ifndef HISTORY_TRACKER_SERVER_HPP_
#define HISTORY_TRACKER_SERVER_HPP_
#include "openthread-core-config.h"
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE && OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE
#include <openthread/history_tracker.h>
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
#include "common/timer.hpp"
#include "thread/tmf.hpp"
#include "utils/history_tracker.hpp"
#include "utils/history_tracker_tlvs.hpp"
namespace ot {
namespace HistoryTracker {
/**
* Represents History Tracker Server.
*/
class Server : public InstanceLocator
{
friend class Tmf::Agent;
public:
explicit Server(Instance &aInstance);
private:
static constexpr uint16_t kAnswerMessageLengthThreshold = 800;
struct AnswerInfo
{
AnswerInfo(void)
: mNow(TimerMilli::GetNow())
, mAnswerIndex(0)
, mQueryId(0)
, mHasQueryId(false)
, mFirstAnswer(nullptr)
{
}
TimeMilli mNow;
uint16_t mAnswerIndex;
uint16_t mQueryId;
bool mHasQueryId;
Message::Priority mPriority;
Coap::Message *mFirstAnswer;
};
Error AllocateAnswer(Coap::Message *&aAnswer, AnswerInfo &aInfo);
bool IsLastAnswer(const Coap::Message &aAnswer) const;
void FreeAllRelatedAnswers(Coap::Message &aFirstAnswer);
void PrepareAndSendAnswers(const Ip6::Address &aDestination, const Message &aRequest);
Error CheckAnswerLength(Coap::Message *&aAnswer, AnswerInfo &aInfo);
void SendNextAnswer(Coap::Message &aAnswer, const Ip6::Address &aDestination);
void PrepareMessageInfoForDest(const Ip6::Address &aDestination, Tmf::MessageInfo &aMessageInfo) const;
Error AppendNetworkInfo(Coap::Message *&aAnswer, AnswerInfo &aInfo, const RequestTlv &aRequestTlv);
Error AppendEmptyTlv(Coap::Message &aAnswer, Tlv::Type aTlvType);
static void HandleAnswerResponse(void *aContext,
otMessage *aMessage,
const otMessageInfo *aMessageInfo,
otError aResult);
void HandleAnswerResponse(Coap::Message &aNextAnswer,
Coap::Message *aResponse,
const Ip6::MessageInfo *aMessageInfo,
Error aResult);
template <Uri kUri> void HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo);
Coap::MessageQueue mAnswerQueue;
};
DeclareTmfHandler(Server, kUriHistoryQuery);
} // namespace HistoryTracker
} // namespace ot
#endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE && OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE
#endif // HISTORY_TRACKER_SERVER_HPP_
+90
View File
@@ -0,0 +1,90 @@
/*
* Copyright (c) 2025, 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 to support History Tracker TLVs.
*/
#include "utils/history_tracker_tlvs.hpp"
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
namespace ot {
namespace HistoryTracker {
void AnswerTlv::Init(uint16_t aIndex, bool aIsLast)
{
SetType(kAnswer);
SetLength(sizeof(*this) - sizeof(Tlv));
SetFlagsIndex((aIndex & kIndexMask) | (aIsLast ? kIsLastFlag : 0));
}
void RequestTlv::Init(uint8_t aTlvType, uint16_t aNumEntries, uint32_t aMaxEntryAge)
{
SetType(kRequest);
SetLength(sizeof(*this) - sizeof(Tlv));
mTlvType = aTlvType;
mNumEntries = BigEndian::HostSwap16(aNumEntries);
mMaxEntryAge = BigEndian::HostSwap32(aMaxEntryAge);
}
void NetworkInfoTlv::InitFrom(const NetworkInfo &aNetworkInfo, uint32_t aEntryAge)
{
Mle::DeviceMode deviceMode;
SetType(kNetworkInfo);
SetLength(sizeof(*this) - sizeof(Tlv));
deviceMode.Set(aNetworkInfo.mMode);
mEntryAge = BigEndian::HostSwap32(aEntryAge);
mRole = aNetworkInfo.mRole;
mMode = deviceMode.Get();
mRloc16 = BigEndian::HostSwap16(aNetworkInfo.mRloc16);
mPartitionId = BigEndian::HostSwap32(aNetworkInfo.mPartitionId);
}
void NetworkInfoTlv::CopyTo(NetworkInfo &aNetworkInfo) const
{
Mle::DeviceMode deviceMode;
deviceMode.Set(mMode);
aNetworkInfo.mRole = static_cast<otDeviceRole>(mRole);
aNetworkInfo.mRloc16 = BigEndian::HostSwap16(mRloc16);
aNetworkInfo.mPartitionId = BigEndian::HostSwap32(mPartitionId);
deviceMode.Get(aNetworkInfo.mMode);
}
} // namespace HistoryTracker
} // namespace ot
#endif // #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
+215
View File
@@ -0,0 +1,215 @@
/*
* Copyright (c) 2025, 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 to support History Tracker TLVs.
*/
#ifndef HISTORY_TRACKER_TLVS_HPP_
#define HISTORY_TRACKER_TLVS_HPP_
#include "openthread-core-config.h"
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
#include <openthread/history_tracker.h>
#include "common/encoding.hpp"
#include "common/tlvs.hpp"
#include "utils/history_tracker.hpp"
namespace ot {
namespace HistoryTracker {
/**
* Implements History Tracker TLV generation and parsing.
*/
OT_TOOL_PACKED_BEGIN
class Tlv : public ot::Tlv
{
public:
/**
* History Tracker TLV Types.
*/
enum Type : uint8_t
{
kQueryId = 0,
kAnswer = 1,
kRequest = 2,
kNetworkInfo = 3,
};
} OT_TOOL_PACKED_END;
/**
* Defines Query ID TLV constants and types.
*/
typedef UintTlvInfo<Tlv::kQueryId, uint16_t> QueryIdTlv;
/**
* Implements Answer TLV generation and parsing.
*/
OT_TOOL_PACKED_BEGIN
class AnswerTlv : public Tlv, public TlvInfo<Tlv::kAnswer>
{
public:
/**
* Initializes the TLV.
*
* @param[in] aIndex The index value.
* @param[in] aIsLast The "IsLast" flag value.
*/
void Init(uint16_t aIndex, bool aIsLast);
/**
* Indicates whether or not the "IsLast" flag is set
*
* @retval TRUE "IsLast" flag is set (this is the last answer for this query).
* @retval FALSE "IsLast" flag is not set (more answer messages are expected for this query).
*/
bool IsLast(void) const { return GetFlagsIndex() & kIsLastFlag; }
/**
* Gets the index.
*
* @returns The index.
*/
uint16_t GetIndex(void) const { return GetFlagsIndex() & kIndexMask; }
private:
static constexpr uint16_t kIsLastFlag = 1 << 15;
static constexpr uint16_t kIndexMask = 0x7fff;
uint16_t GetFlagsIndex(void) const { return BigEndian::HostSwap16(mFlagsIndex); }
void SetFlagsIndex(uint16_t aFlagsIndex) { mFlagsIndex = BigEndian::HostSwap16(aFlagsIndex); }
uint16_t mFlagsIndex;
} OT_TOOL_PACKED_END;
/**
* Implements Request TLV generation and parsing.
*/
OT_TOOL_PACKED_BEGIN
class RequestTlv : public Tlv, public TlvInfo<Tlv::kRequest>
{
public:
/**
* Initializes the TLV.
*
* @param[in] aTlvType The TLV type to request.
* @param[in] aNumEntries Maximum number of entries to include in the reply (zero indicates all).
* @param[in] aMaxEntryAge Maximum entry age to include in the reply (zero indicates no limit).
*/
void Init(uint8_t aTlvType, uint16_t aNumEntries, uint32_t aMaxEntryAge);
/**
* Indicates whether or not the TLV appears to be well-formed.
*
* @retval TRUE If the TLV appears to be well-formed.
* @retval FALSE If the TLV does not appear to be well-formed.
*/
bool IsValid(void) const { return GetLength() >= sizeof(*this) - sizeof(Tlv); }
/**
* Gets the requested TLV type.
*
* @returns The requested TLV type.
*/
uint8_t GetTlvType(void) const { return mTlvType; }
/**
* Gets the maximum number of entries to include in the reply.
*
* @returns The maximum number of entries (zero indicates all).
*/
uint16_t GetNumEntries(void) const { return BigEndian::HostSwap16(mNumEntries); }
/**
* Gets the maximum entry age to include in the reply.
*
* @returns The maximum entry age in milliseconds (zero indicates no age limit).
*/
uint32_t GetMaxEntryAge(void) const { return BigEndian::HostSwap32(mMaxEntryAge); }
private:
uint8_t mTlvType;
uint16_t mNumEntries;
uint32_t mMaxEntryAge;
} OT_TOOL_PACKED_END;
/**
* Implements Network Info TLV generation and parsing.
*/
OT_TOOL_PACKED_BEGIN
class NetworkInfoTlv : public Tlv, public TlvInfo<Tlv::kNetworkInfo>
{
public:
/**
* Initializes the TLV from a `NetworkInfo` object and entry age.
*
* @param[in] aNetworkInfo The NetworkInfo object to initialize from.
* @param[in] aEntryAge The age of the entry in milliseconds.
*/
void InitFrom(const NetworkInfo &aNetworkInfo, uint32_t aEntryAge);
/**
* Indicates whether or not the TLV appears to be well-formed.
*
* @retval TRUE If the TLV appears to be well-formed.
* @retval FALSE If the TLV does not appear to be well-formed.
*/
bool IsValid(void) const { return GetLength() >= sizeof(*this) - sizeof(Tlv); }
/**
* Copies the TLV data to a `NetworkInfo` object.
*
* @param[out] aNetworkInfo The `NetworkInfo` object to copy data to.
*/
void CopyTo(NetworkInfo &aNetworkInfo) const;
/**
* Gets the entry age.
*
* @returns The entry age in milliseconds.
*/
uint32_t GetEntryAge(void) const { return BigEndian::HostSwap32(mEntryAge); }
private:
uint32_t mEntryAge;
uint8_t mRole;
uint8_t mMode;
uint16_t mRloc16;
uint32_t mPartitionId;
} OT_TOOL_PACKED_END;
} // namespace HistoryTracker
} // namespace ot
#endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
#endif // HISTORY_TRACKER_TLVS_HPP_
+116
View File
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
#
# Copyright (c) 2025, 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.
from cli import verify
from cli import verify_within
import cli
import time
# -----------------------------------------------------------------------------------------------------------------------
# Test description: History Tracker client/server behavior
#
# Network topology
#
# r1 ---- r2
#
test_name = __file__[:-3] if __file__.endswith('.py') else __file__
print('-' * 120)
print('Starting \'{}\''.format(test_name))
# -----------------------------------------------------------------------------------------------------------------------
# Creating `cli.Node` instances
speedup = 40
cli.Node.set_time_speedup_factor(speedup)
r1 = cli.Node()
r2 = cli.Node()
# -----------------------------------------------------------------------------------------------------------------------
# Form topology
r1.form('hist-srv')
r2.join(r1)
verify(r1.get_state() == 'leader')
verify(r2.get_state() == 'router')
# -----------------------------------------------------------------------------------------------------------------------
# Test Implementation
r2_rlco16 = int(r2.get_rloc16(), 16)
local_table = r2.cli('history netinfo')
verify(len(local_table) == 5)
local_table = local_table[2:]
# Remove the age field from the table
for i in range(3):
start_index = local_table[i].find(' | ')
local_table[i] = local_table[i][start_index:]
# Check that the result of query matches the local table
table = r1.cli('history query netinfo', r2_rlco16)
verify(len(table) == 5)
table = table[2:]
for i in range(3):
verify(table[i].endswith(local_table[i]))
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Validate different CLI command variations
# Test query with the 'list' format
result = r1.cli('history query netinfo list', r2_rlco16)
verify(len(result) == 3)
# Test specifying max entries as 2
table = r1.cli('history query netinfo', r2_rlco16, 2)
verify(len(table) == 4)
# Test specifying max entries as 10
table = r1.cli('history query netinfo', r2_rlco16, 10)
verify(len(table) == 5)
# Test specifying max entry age as zero (no age limit)
table = r1.cli('history query netinfo', r2_rlco16, 0, 0)
verify(len(table) == 5)
# Test specifying max entry age as 1 millisecond
table = r1.cli('history query netinfo', r2_rlco16, 0, 1)
verify(len(table) == 2)
# -----------------------------------------------------------------------------------------------------------------------
# Test finished
cli.Node.finalize_all_nodes()
print('\'{}\' passed.'.format(test_name))
@@ -72,9 +72,10 @@
#define OPENTHREAD_CONFIG_NAT64_PORT_TRANSLATION_ENABLE 1
// The following features are set explicitly on posix `toranj`
// to validate the build with these config. The `toranj` build
// under simulation platform covers the opposite configs.
// The following features (e.g., `USE_HEAP`) are enabled or disabled
// explicitly on POSIX `toranj` to validate the build with or without
// them. The `toranj` build under the simulation platform covers the
// opposite/alternative configurations (e.g., allows `USE_HEAP`).
#define OPENTHREAD_CONFIG_BORDER_ROUTING_USE_HEAP_ENABLE 0
@@ -82,4 +83,8 @@
#define OPENTHREAD_CONFIG_DNS_CLIENT_BIND_UDP_TO_THREAD_NETIF 0
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE 1
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE 0
#endif /* OPENTHREAD_CORE_TORANJ_CONFIG_POSIX_H_ */
@@ -43,6 +43,10 @@
#define OPENTHREAD_CONFIG_PLATFORM_INFO "SIMULATION-toranj"
#endif
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_SERVER_ENABLE 1
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE 1
#define OPENTHREAD_CONFIG_SRP_SERVER_FAST_START_MODE_ENABLE 1
#define OPENTHREAD_CONFIG_COAP_API_ENABLE 1
+1
View File
@@ -205,6 +205,7 @@ if [ "$TORANJ_CLI" = 1 ]; then
run cli/test-037-mtd-annc-join-older-timestamp.py
run cli/test-038-simultaneous-parent-and-child-reset.py
run cli/test-039-border-agent-evict-active-commissioner.py
run cli/test-040-history-tracker-query.py
run cli/test-400-srp-client-server.py
run cli/test-401-srp-server-address-cache-snoop.py
run cli/test-500-two-brs-two-networks.py