mirror of
https://github.com/espressif/openthread.git
synced 2026-07-28 06:37:46 +00:00
[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:
@@ -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);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"; }
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user