mirror of
https://github.com/espressif/openthread.git
synced 2026-06-06 05:24:51 +00:00
[utils] adding HistoryTracker module (#6807)
This commit adds History Tracker feature and its CLI support. This feature records history of different events as the Thread network operates (e.g., history of RX and TX IPv6 messages or network info changes). Recorded entries are timestamped. When the history list is read, the timestamps are given as the entry age relative to the time the list is being read. For example in CLI a timestamp can be shown as `02:31:50.628 ago` indicating the entry was recorded 2 hours, 31 min, 50 sec, and 628 msec ago. Number of days is added for events that are older than 24 hours, e.g., `31 days 03:00:23.931 ago`. Timestamps use millisecond accuracy and are tacked up to 49 days. If an event is older than 49 days, the entry is still tracked in the list but the timestamp is shown as old or `more than 49 days ago`. The `HistoryTracker` currently maintains 3 lists. The Network Info history tracks changes to Device Role, Mode, RLOC16 and Partition ID. The RX/TX history list records information about the received/sent IPv6 messages: - Message type (UDP, TCP, ICMP6 (and its subtype), etc.) - Source and destination IPv6 addresses and port numbers - IPv6 payload length - The message checksum (for UDP, TCP, or ICMP6). - Whether or not the link-layer security was used - Message priority: low, norm, high, net (for control messages) - Short address (RLOC16) of neighbor who send/received the msg - Received Signal Strength (in dBm) for RX only - Radio link info (15.4/TREL) on which msg was sent/received (useful when `OPENTHREAD_CONFIG_MULTI_RADIO` is enabled) Config `HISTORY_TRACKER_EXCLUDE_THREAD_CONTROL_MESSAGES` can be used to configure `HistoryTracker` to exclude Thread Control message (e.g., MLE, TMF) from TX and RX history. The number of entries recorded for each history list is configurable through a set of OpenThread config options, e.g., number of entries in Network Info history list is specified by OpenThread config option `OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_INFO_LIST_SIZE`. The `HistoryTracker` will keep the most recent entries overwriting oldest ones when the list gets full. This commit also adds support for `HistoryTracker` in CLI. The CLI commands provide two style for printing the history information: A table format (more human-readable) and list style (better suited for parsing by machine/code). `README_HISTORY.md` is added to document the commands and the info provided by each history list entry. This commit also adds `test_history_tracker.py` test-case which covers the behavior of `HistoryTracker`.
This commit is contained in:
committed by
GitHub
parent
59f7a9aed6
commit
2798cc9c05
@@ -62,6 +62,7 @@ jobs:
|
||||
export ASAN_SYMBOLIZER_PATH=`which llvm-symbolizer`
|
||||
export ASAN_OPTIONS=symbolize=1
|
||||
export DISTCHECK_CONFIGURE_FLAGS= CPPFLAGS=-DOPENTHREAD_SIMULATION_VIRTUAL_TIME=1
|
||||
export DISTCHECK_BUILD=1
|
||||
./bootstrap
|
||||
VERBOSE=1 make -f examples/Makefile-simulation distcheck
|
||||
|
||||
|
||||
@@ -178,6 +178,7 @@ LOCAL_SRC_FILES := \
|
||||
src/core/api/entropy_api.cpp \
|
||||
src/core/api/error_api.cpp \
|
||||
src/core/api/heap_api.cpp \
|
||||
src/core/api/history_tracker_api.cpp \
|
||||
src/core/api/icmp6_api.cpp \
|
||||
src/core/api/instance_api.cpp \
|
||||
src/core/api/ip6_api.cpp \
|
||||
@@ -342,6 +343,7 @@ LOCAL_SRC_FILES := \
|
||||
src/core/utils/child_supervision.cpp \
|
||||
src/core/utils/flash.cpp \
|
||||
src/core/utils/heap.cpp \
|
||||
src/core/utils/history_tracker.cpp \
|
||||
src/core/utils/jam_detector.cpp \
|
||||
src/core/utils/lookup_table.cpp \
|
||||
src/core/utils/otns.cpp \
|
||||
@@ -498,6 +500,7 @@ LOCAL_SRC_FILES := \
|
||||
src/cli/cli_coap_secure.cpp \
|
||||
src/cli/cli_commissioner.cpp \
|
||||
src/cli/cli_dataset.cpp \
|
||||
src/cli/cli_history.cpp \
|
||||
src/cli/cli_joiner.cpp \
|
||||
src/cli/cli_network_data.cpp \
|
||||
src/cli/cli_srp_client.cpp \
|
||||
|
||||
@@ -211,6 +211,11 @@ if(OT_EXTERNAL_HEAP)
|
||||
target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE=1")
|
||||
endif()
|
||||
|
||||
option(OT_HISTORY_TRACKER "enable history tracker support")
|
||||
if(OT_HISTORY_TRACKER)
|
||||
target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE=1")
|
||||
endif()
|
||||
|
||||
option(OT_IP6_FRAGM "enable ipv6 fragmentation support")
|
||||
if(OT_IP6_FRAGM)
|
||||
target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE=1")
|
||||
|
||||
@@ -52,6 +52,7 @@ DIAGNOSTIC ?= 1
|
||||
DNS_CLIENT ?= 1
|
||||
DNSSD_SERVER ?= 1
|
||||
ECDSA ?= 1
|
||||
HISTORY_TRACKER ?= 1
|
||||
IP6_FRAGM ?= 1
|
||||
JAM_DETECTION ?= 1
|
||||
JOINER ?= 1
|
||||
|
||||
@@ -37,6 +37,7 @@ This page lists the available common switches with description. Unless stated ot
|
||||
| ECDSA | OT_ECDSA | Enables support for Elliptic Curve Digital Signature Algorithm. Enable this switch if ECDSA digital signature is used by application. |
|
||||
| EXTERNAL_HEAP | OT_EXTERNAL_HEAP | Enables support for external heap. Enable this switch if the platform uses its own heap. Make sure to specify the external heap Calloc and Free functions to be used by the OpenThread stack. |
|
||||
| FULL_LOGS | OT_FULL_LOGS | Enables all log levels and regions. This switch sets the log level to OT_LOG_LEVEL_DEBG and turns on all region flags. See [Logging guide](https://openthread.io/guides/build/logs) to learn more. |
|
||||
| HISTORY_TRACKER | OT_HISTORY_TRACKER | Enables support for History Tracker. |
|
||||
| IP6_FRAGM | OT_IP6_FRAGM | Enables support for IPv6 fragmentation. |
|
||||
| JAM_DETECTION | OT_JAM_DETECTION | Enables support for [Jam Detection](https://openthread.io/guides/build/features/jam-detection). Enable this switch if a device requires the ability to detect signal jamming on a specific channel. |
|
||||
| JOINER | OT_JOINER | Enables [support for Joiner](https://openthread.io/reference/group/api-joiner). Enable this switch on a device that has to be commissioned to join the network. |
|
||||
|
||||
@@ -55,6 +55,7 @@ DUA ?= 0
|
||||
DYNAMIC_LOG_LEVEL ?= 0
|
||||
ECDSA ?= 0
|
||||
EXTERNAL_HEAP ?= 0
|
||||
HISTORY_TRACKER ?= 0
|
||||
IP6_FRAGM ?= 0
|
||||
JAM_DETECTION ?= 0
|
||||
JOINER ?= 0
|
||||
@@ -211,6 +212,10 @@ ifeq ($(EXTERNAL_HEAP),1)
|
||||
COMMONCFLAGS += -DOPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE=1
|
||||
endif
|
||||
|
||||
ifeq ($(HISTORY_TRACKER),1)
|
||||
COMMONCFLAGS += -DOPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE=1
|
||||
endif
|
||||
|
||||
ifeq ($(IP6_FRAGM),1)
|
||||
COMMONCFLAGS += -DOPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE=1
|
||||
endif
|
||||
|
||||
@@ -58,6 +58,7 @@ openthread_headers = \
|
||||
openthread/entropy.h \
|
||||
openthread/error.h \
|
||||
openthread/heap.h \
|
||||
openthread/history_tracker.h \
|
||||
openthread/icmp6.h \
|
||||
openthread/instance.h \
|
||||
openthread/ip6.h \
|
||||
|
||||
@@ -79,6 +79,7 @@ source_set("openthread") {
|
||||
"entropy.h",
|
||||
"error.h",
|
||||
"heap.h",
|
||||
"history_tracker.h",
|
||||
"icmp6.h",
|
||||
"instance.h",
|
||||
"ip6.h",
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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)
|
||||
*/
|
||||
|
||||
#ifndef OPENTHREAD_HISTORY_TRACKER_H_
|
||||
#define OPENTHREAD_HISTORY_TRACKER_H_
|
||||
|
||||
#include <openthread/instance.h>
|
||||
#include <openthread/ip6.h>
|
||||
#include <openthread/thread.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief
|
||||
* This header defines the public API for History Tracker.
|
||||
*
|
||||
* History Tracker module records history of different events (e.g. RX and TX messages or network info changes, etc.)
|
||||
* as the Thread network operates. All tracked entries are timestamped.
|
||||
*
|
||||
* The functions in this module are available when `OPENTHREAD_CONFIG_HISTOR_TRACKER_ENABLE` is enabled.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* This constant specifies the maximum age of entries which is 49 days (in msec).
|
||||
*
|
||||
* Entries older than the max age will give this value as their age.
|
||||
*
|
||||
*/
|
||||
#define OT_HISTORY_TRACKER_MAX_AGE (49 * 24 * 60 * 60 * 1000u)
|
||||
|
||||
/**
|
||||
* This type represents an iterator to iterate through a history list.
|
||||
*
|
||||
* The fields in this type are opaque (intended for use by OpenThread core) and therefore should not be accessed/used
|
||||
* by caller.
|
||||
*
|
||||
* Before using an iterator, it MUST be initialized using `otHistoryTrackerInitIterator()`,
|
||||
*
|
||||
*/
|
||||
typedef struct otHistoryTrackerIterator
|
||||
{
|
||||
uint32_t mData32;
|
||||
uint16_t mData16;
|
||||
} otHistoryTrackerIterator;
|
||||
|
||||
/**
|
||||
* This structure represents Thread network info.
|
||||
*
|
||||
*/
|
||||
typedef struct otHistoryTrackerNetworkInfo
|
||||
{
|
||||
otDeviceRole mRole; ///< Device Role.
|
||||
otLinkModeConfig mMode; ///< Device Mode.
|
||||
uint16_t mRloc16; ///< Device RLOC16.
|
||||
uint32_t mPartitionId; ///< Partition ID (valid when attached).
|
||||
} otHistoryTrackerNetworkInfo;
|
||||
|
||||
/**
|
||||
* Constants representing message priority used in `otHistoryTrackerMessageInfo` struct.
|
||||
*
|
||||
*/
|
||||
enum
|
||||
{
|
||||
OT_HISTORY_TRACKER_MSG_PRIORITY_LOW = OT_MESSAGE_PRIORITY_LOW, ///< Low priority level.
|
||||
OT_HISTORY_TRACKER_MSG_PRIORITY_NORMAL = OT_MESSAGE_PRIORITY_NORMAL, ///< Normal priority level.
|
||||
OT_HISTORY_TRACKER_MSG_PRIORITY_HIGH = OT_MESSAGE_PRIORITY_HIGH, ///< High priority level.
|
||||
OT_HISTORY_TRACKER_MSG_PRIORITY_NET = OT_MESSAGE_PRIORITY_HIGH + 1, ///< Network Control priority level.
|
||||
};
|
||||
|
||||
/**
|
||||
* This structure represents a RX/TX IPv6 message info.
|
||||
*
|
||||
* Some of the fields in this struct are applicable to a RX message or a TX message only, e.g., `mAveRxRss` is the
|
||||
* average RSS of all fragment frames that form a received message and is only applicable for a RX message.
|
||||
*
|
||||
*/
|
||||
typedef struct otHistoryTrackerMessageInfo
|
||||
{
|
||||
uint16_t mPayloadLength; ///< IPv6 payload length (exclude IP6 header itself).
|
||||
uint16_t mNeighborRloc16; ///< RLOC16 of neighbor which sent/received the msg (`0xfffe` if no RLOC16).
|
||||
otSockAddr mSource; ///< Source IPv6 address and port (if UDP/TCP)
|
||||
otSockAddr mDestination; ///< Destination IPv6 address and port (if UDP/TCP).
|
||||
uint16_t mChecksum; ///< Message checksum (valid only for UDP/TCP/ICMP6).
|
||||
uint8_t mIpProto; ///< IP Protocol number (`OT_IP6_PROTO_*` enumeration).
|
||||
uint8_t mIcmp6Type; ///< ICMP6 type if msg is ICMP6, zero otherwise (`OT_ICMP6_TYPE_*` enumeration).
|
||||
int8_t mAveRxRss; ///< RSS of received message or OT_RADIO_INVALI_RSSI if not known.
|
||||
bool mLinkSecurity : 1; ///< Indicates whether msg used link security.
|
||||
bool mTxSuccess : 1; ///< Indicates TX success (e.g., ack received). Applicable for TX msg only.
|
||||
uint8_t mPriority : 2; ///< Message priority (`OT_HISTORY_TRACKER_MSG_PRIORITY_*` enumeration).
|
||||
bool mRadioIeee802154 : 1; ///< Indicates whether msg was sent/received over a 15.4 radio link.
|
||||
bool mRadioTrelUdp6 : 1; ///< Indicates whether msg was sent/received over a TREL radio link.
|
||||
} otHistoryTrackerMessageInfo;
|
||||
|
||||
/**
|
||||
* This function initializes an `otHistoryTrackerIterator`.
|
||||
*
|
||||
* An iterator MUST be initialized before it is used.
|
||||
*
|
||||
* An iterator can be initialized again to start from the beginning of the list.
|
||||
*
|
||||
* When iterating over entries in a list, to ensure the entry ages are consistent, the age is given relative to the
|
||||
* time the iterator was initialized, i.e., the entry age is provided as the duration (in milliseconds) from the event
|
||||
* (when entry was recorded) to the iterator initialization time.
|
||||
*
|
||||
* @param[in] aIterator A pointer to the iterator to initialize (MUST NOT be NULL).
|
||||
*
|
||||
*/
|
||||
void otHistoryTrackerInitIterator(otHistoryTrackerIterator *aIterator);
|
||||
|
||||
/**
|
||||
* This function iterates over the entries in the network info history list.
|
||||
*
|
||||
* @param[in] aInstance A pointer to the OpenThread instance.
|
||||
* @param[inout] aIterator A pointer to an iterator. MUST be initialized or the behavior is undefined.
|
||||
* @param[out] aEntryAge A pointer to a variable to output the entry's age. MUST NOT be NULL.
|
||||
* Age is provided as the duration (in milliseconds) from when entry was recorded to
|
||||
* @p aIterator initialization time. It is set to `OT_HISTORY_TRACKER_MAX_AGE` for entries
|
||||
* older than max age.
|
||||
*
|
||||
* @returns A pointer to `otHistoryTrackerNetworkInfo` entry or `NULL` if no more entries in the list.
|
||||
*
|
||||
*/
|
||||
const otHistoryTrackerNetworkInfo *otHistoryTrackerIterateNetInfoHistory(otInstance * aInstance,
|
||||
otHistoryTrackerIterator *aIterator,
|
||||
uint32_t * aEntryAge);
|
||||
|
||||
/**
|
||||
* This function iterates over the entries in the RX message history list.
|
||||
*
|
||||
* @param[in] aInstance A pointer to the OpenThread instance.
|
||||
* @param[inout] aIterator A pointer to an iterator. MUST be initialized or the behavior is undefined.
|
||||
* @param[out] aEntryAge A pointer to a variable to output the entry's age. MUST NOT be NULL.
|
||||
* Age is provided as the duration (in milliseconds) from when entry was recorded to
|
||||
* @p aIterator initialization time. It is set to `OT_HISTORY_TRACKER_MAX_AGE` for entries
|
||||
* older than max age.
|
||||
*
|
||||
* @returns The `otHistoryTrackerMessageInfo` entry or `NULL` if no more entries in the list.
|
||||
*
|
||||
*/
|
||||
const otHistoryTrackerMessageInfo *otHistoryTrackerIterateRxHistory(otInstance * aInstance,
|
||||
otHistoryTrackerIterator *aIterator,
|
||||
uint32_t * aEntryAge);
|
||||
|
||||
/**
|
||||
* This function iterates over the entries in the TX message history list.
|
||||
*
|
||||
* @param[in] aInstance A pointer to the OpenThread instance.
|
||||
* @param[inout] aIterator A pointer to an iterator. MUST be initialized or the behavior is undefined.
|
||||
* @param[out] aEntryAge A pointer to a variable to output the entry's age. MUST NOT be NULL.
|
||||
* Age is provided as the duration (in milliseconds) from when entry was recorded to
|
||||
* @p aIterator initialization time. It is set to `OT_HISTORY_TRACKER_MAX_AGE` for entries
|
||||
* older than max age.
|
||||
*
|
||||
* @returns The `otHistoryTrackerMessageInfo` entry or `NULL` if no more entries in the list.
|
||||
*
|
||||
*/
|
||||
const otHistoryTrackerMessageInfo *otHistoryTrackerIterateTxHistory(otInstance * aInstance,
|
||||
otHistoryTrackerIterator *aIterator,
|
||||
uint32_t * aEntryAge);
|
||||
|
||||
#define OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE 21 ///< Recommended size for string representation of an entry age.
|
||||
|
||||
/**
|
||||
* This function converts a given entry age to a human-readable string.
|
||||
*
|
||||
* The entry age string follows the format "<hh>:<mm>:<ss>.<mmmm>" for hours, minutes, seconds and millisecond (if
|
||||
* shorter than one day) or "<dd> days <hh>:<mm>:<ss>.<mmmm>" (if longer than one day).
|
||||
*
|
||||
* If the resulting string does not fit in @p aBuffer (within its @p aSize characters), the string will be truncated
|
||||
* but the outputted string is always null-terminated.
|
||||
*
|
||||
* @param[in] aEntryAge The entry age (duration in msec).
|
||||
* @param[out] aBuffer A pointer to a char array to output the string (MUST NOT be NULL).
|
||||
* @param[in] aSize The size of @p aBuffer (in bytes). Recommended to use `OT_IP6_ADDRESS_STRING_SIZE`.
|
||||
*
|
||||
*/
|
||||
void otHistoryTrackerEntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // OPENTHREAD_HISTORY_TRACKER_H_
|
||||
@@ -53,7 +53,7 @@ extern "C" {
|
||||
* @note This number versions both OpenThread platform and user APIs.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_API_VERSION (150)
|
||||
#define OPENTHREAD_API_VERSION (151)
|
||||
|
||||
/**
|
||||
* @addtogroup api-instance
|
||||
|
||||
@@ -226,6 +226,23 @@ typedef struct otMessageInfo
|
||||
bool mMulticastLoop : 1; ///< TRUE to allow looping back multicast, FALSE otherwise.
|
||||
} otMessageInfo;
|
||||
|
||||
/**
|
||||
* Internet Protocol Numbers.
|
||||
*
|
||||
*/
|
||||
enum
|
||||
{
|
||||
OT_IP6_PROTO_HOP_OPTS = 0, ///< IPv6 Hop-by-Hop Option
|
||||
OT_IP6_PROTO_TCP = 6, ///< Transmission Control Protocol
|
||||
OT_IP6_PROTO_UDP = 17, ///< User Datagram
|
||||
OT_IP6_PROTO_IP6 = 41, ///< IPv6 encapsulation
|
||||
OT_IP6_PROTO_ROUTING = 43, ///< Routing Header for IPv6
|
||||
OT_IP6_PROTO_FRAGMENT = 44, ///< Fragment Header for IPv6
|
||||
OT_IP6_PROTO_ICMP6 = 58, ///< ICMP for IPv6
|
||||
OT_IP6_PROTO_NONE = 59, ///< No Next Header for IPv6
|
||||
OT_IP6_PROTO_DST_OPTS = 60, ///< Destination Options for IPv6
|
||||
};
|
||||
|
||||
/**
|
||||
* This function brings up/down the IPv6 interface.
|
||||
*
|
||||
@@ -797,6 +814,16 @@ otError otIp6RegisterMulticastListeners(otInstance *
|
||||
*/
|
||||
otError otIp6SetMeshLocalIid(otInstance *aInstance, const otIp6InterfaceIdentifier *aIid);
|
||||
|
||||
/**
|
||||
* This function converts a given IP protocol number to a human-readable string.
|
||||
*
|
||||
* @param[in] aIpProto An IP protocol number (`OT_IP6_PROTO_*` enumeration).
|
||||
*
|
||||
* @returns A string representing @p aIpProto.
|
||||
*
|
||||
*/
|
||||
const char *otIp6ProtoToString(uint8_t aIpProto);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
@@ -709,6 +709,16 @@ otError otThreadGetNextNeighborInfo(otInstance *aInstance, otNeighborInfoIterato
|
||||
*/
|
||||
otDeviceRole otThreadGetDeviceRole(otInstance *aInstance);
|
||||
|
||||
/**
|
||||
* Convert the device role to human-readable string.
|
||||
*
|
||||
* @param[in] aRole The device role to convert.
|
||||
*
|
||||
* @returns A string representing @p aRole.
|
||||
*
|
||||
*/
|
||||
const char *otThreadDeviceRoleToString(otDeviceRole aRole);
|
||||
|
||||
/**
|
||||
* This function get the Thread Leader Data.
|
||||
*
|
||||
|
||||
@@ -50,6 +50,7 @@ do_scan_build()
|
||||
"-DOPENTHREAD_CONFIG_DNS_CLIENT_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_ECDSA_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_IP6_SLAAC_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_JAM_DETECTION_ENABLE=1"
|
||||
|
||||
@@ -55,6 +55,7 @@ build_all_features()
|
||||
"-DOPENTHREAD_CONFIG_DNS_CLIENT_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_ECDSA_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_IP6_FRAGMENTATION_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_IP6_SLAAC_ENABLE=1"
|
||||
"-DOPENTHREAD_CONFIG_JAM_DETECTION_ENABLE=1"
|
||||
|
||||
@@ -82,6 +82,7 @@ readonly OT_POSIX_SIM_COMMON_OPTIONS=(
|
||||
"-DOT_DIAGNOSTIC=ON"
|
||||
"-DOT_DNS_CLIENT=ON"
|
||||
"-DOT_ECDSA=ON"
|
||||
"-DOT_HISTORY_TRACKER=ON"
|
||||
"-DOT_IP6_FRAGM=ON"
|
||||
"-DOT_JAM_DETECTION=ON"
|
||||
"-DOT_JOINER=ON"
|
||||
|
||||
@@ -99,6 +99,7 @@ readonly OT_CLANG_TIDY_BUILD_OPTS=(
|
||||
'-DOT_DUA=ON'
|
||||
'-DOT_MLR=ON'
|
||||
'-DOT_ECDSA=ON'
|
||||
'-DOT_HISTORY_TRACKER=ON'
|
||||
'-DOT_IP6_FRAGM=ON'
|
||||
'-DOT_JAM_DETECTION=ON'
|
||||
'-DOT_JOINER=ON'
|
||||
|
||||
@@ -59,6 +59,7 @@ build_simulation()
|
||||
"-DOT_DNS_CLIENT=ON"
|
||||
"-DOT_DNSSD_SERVER=ON"
|
||||
"-DOT_ECDSA=ON"
|
||||
"-DOT_HISTORY_TRACKER=ON"
|
||||
"-DOT_MESSAGE_USE_HEAP=ON"
|
||||
"-DOT_NETDATA_PUBLISHER=ON"
|
||||
"-DOT_PING_SENDER=ON"
|
||||
|
||||
@@ -39,6 +39,8 @@ openthread_cli_sources = [
|
||||
"cli_config.h",
|
||||
"cli_dataset.cpp",
|
||||
"cli_dataset.hpp",
|
||||
"cli_history.cpp",
|
||||
"cli_history.hpp",
|
||||
"cli_joiner.cpp",
|
||||
"cli_joiner.hpp",
|
||||
"cli_network_data.cpp",
|
||||
|
||||
@@ -37,6 +37,7 @@ set(COMMON_SOURCES
|
||||
cli_coap_secure.cpp
|
||||
cli_commissioner.cpp
|
||||
cli_dataset.cpp
|
||||
cli_history.cpp
|
||||
cli_joiner.cpp
|
||||
cli_network_data.cpp
|
||||
cli_srp_client.cpp
|
||||
|
||||
@@ -145,6 +145,7 @@ SOURCES_COMMON = \
|
||||
cli_coap_secure.cpp \
|
||||
cli_commissioner.cpp \
|
||||
cli_dataset.cpp \
|
||||
cli_history.cpp \
|
||||
cli_joiner.cpp \
|
||||
cli_network_data.cpp \
|
||||
cli_srp_client.cpp \
|
||||
@@ -168,6 +169,7 @@ noinst_HEADERS = \
|
||||
cli_commissioner.hpp \
|
||||
cli_config.h \
|
||||
cli_dataset.hpp \
|
||||
cli_history.hpp \
|
||||
cli_joiner.hpp \
|
||||
cli_network_data.hpp \
|
||||
cli_srp_client.hpp \
|
||||
|
||||
@@ -52,6 +52,7 @@ Done
|
||||
- [factoryreset](#factoryreset)
|
||||
- [fake](#fake)
|
||||
- [fem](#fem)
|
||||
- [history](README_HISTORY.md)
|
||||
- [ifconfig](#ifconfig)
|
||||
- [ipaddr](#ipaddr)
|
||||
- [ipmaddr](#ipmaddr)
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
# OpenThread CLI - History Tracker
|
||||
|
||||
History Tracker module records history of different events (e.g., RX and TX IPv6 messages or network info changes, etc.) as the Thread network operates. All tracked entries are timestamped.
|
||||
|
||||
All commands under `history` require `OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE` feature to be enabled.
|
||||
|
||||
The number of entries recorded for each history list is configurable through a set of OpenThread config options, e.g. `OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_INFO_LIST_SIZE` specifies the number of entries in Network Info history list. The History Tracker will keep the most recent entries overwriting oldest one when the list gets full.
|
||||
|
||||
## Command List
|
||||
|
||||
Usage : `history [command] ...`
|
||||
|
||||
- [help](#help)
|
||||
- [netinfo](#netinfo)
|
||||
- [rx](#rx)
|
||||
- [rxtx](#rxtx)
|
||||
- [tx](#tx)
|
||||
|
||||
## Timestamp Format
|
||||
|
||||
Recorded entries are timestamped. When the history list is printed, the timestamps are shown relative the time the command was issues (i.e., when the list was printed) indicating how long ago the entry was recorded.
|
||||
|
||||
```bash
|
||||
> history netinfo
|
||||
| Age | Role | Mode | RLOC16 | Partition ID |
|
||||
+----------------------+----------+------+--------+--------------+
|
||||
| 02:31:50.628 | leader | rdn | 0x2000 | 151029327 |
|
||||
| 02:31:53.262 | detached | rdn | 0xfffe | 0 |
|
||||
| 02:31:54.663 | detached | rdn | 0x2000 | 0 |
|
||||
Done
|
||||
```
|
||||
|
||||
For example `02:31:50.628` indicates the event was recorded "2 hours, 31 minutes, 50 seconds, and 628 milliseconds ago". Number of days is added for events that are older than 24 hours, e.g., `1 day 11:25:31.179`, or `31 days 03:00:23.931`.
|
||||
|
||||
Timestamps use millisecond accuracy and are tacked up to 49 days. If the event is older than 49 days, the entry is still tracked in the list but the timestamp is shown as `more than 49 days`.
|
||||
|
||||
## Command Details
|
||||
|
||||
### help
|
||||
|
||||
Usage: `history help`
|
||||
|
||||
Print SRP client help menu.
|
||||
|
||||
```bash
|
||||
> history help
|
||||
help
|
||||
netinfo
|
||||
rx
|
||||
rxtx
|
||||
tx
|
||||
Done
|
||||
>
|
||||
```
|
||||
|
||||
### netinfo
|
||||
|
||||
Usage `history netinfo [list] [<num-entries>]`
|
||||
|
||||
Print the Network Info history. Each Network Info provides
|
||||
|
||||
- Device Role
|
||||
- MLE Link Mode
|
||||
- RLOC16
|
||||
- Partition ID
|
||||
|
||||
Print the Network Info history as a table.
|
||||
|
||||
```bash
|
||||
> history netinfo
|
||||
| Age | Role | Mode | RLOC16 | Partition ID |
|
||||
+----------------------+----------+------+--------+--------------+
|
||||
| 00:00:10.069 | router | rdn | 0x6000 | 151029327 |
|
||||
| 00:02:09.337 | child | rdn | 0x2001 | 151029327 |
|
||||
| 00:02:09.338 | child | rdn | 0x2001 | 151029327 |
|
||||
| 00:07:40.806 | child | - | 0x2001 | 151029327 |
|
||||
| 00:07:42.297 | detached | - | 0x6000 | 0 |
|
||||
| 00:07:42.968 | disabled | - | 0x6000 | 0 |
|
||||
Done
|
||||
```
|
||||
|
||||
Print the Network Info history as a list.
|
||||
|
||||
```bash
|
||||
> history netinfo list
|
||||
00:00:59.467 -> role:router mode:rdn rloc16:0x6000 partition-id:151029327
|
||||
00:02:58.735 -> role:child mode:rdn rloc16:0x2001 partition-id:151029327
|
||||
00:02:58.736 -> role:child mode:rdn rloc16:0x2001 partition-id:151029327
|
||||
00:08:30.204 -> role:child mode:- rloc16:0x2001 partition-id:151029327
|
||||
00:08:31.695 -> role:detached mode:- rloc16:0x6000 partition-id:0
|
||||
00:08:32.366 -> role:disabled mode:- rloc16:0x6000 partition-id:0
|
||||
Done
|
||||
```
|
||||
|
||||
Print only the latest 2 entries.
|
||||
|
||||
```bash
|
||||
> history netinfo 2
|
||||
| Age | Role | Mode | RLOC16 | Partition ID |
|
||||
+----------------------+----------+------+--------+--------------+
|
||||
| 00:02:05.451 | router | rdn | 0x6000 | 151029327 |
|
||||
| 00:04:04.719 | child | rdn | 0x2001 | 151029327 |
|
||||
Done
|
||||
```
|
||||
|
||||
### rx
|
||||
|
||||
Usage `history rx [list] [<num-entries>]`
|
||||
|
||||
Print the IPv6 message RX history in either table or list format. Entries provide same information and follow same format as in `history rxtx` command.
|
||||
|
||||
Print the IPv6 message RX history as a table:
|
||||
|
||||
```bash
|
||||
> history rx
|
||||
| Age | Type | Len | Chksum | Sec | Prio | RSS |Dir | Neighb | Radio |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 50 | 0xbd26 | no | net | -20 | RX | 0x4800 | 15.4 |
|
||||
| 00:00:07.640 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788 |
|
||||
| | dst: [ff02:0:0:0:0:0:0:1]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | HopOpts | 44 | 0x0000 | yes | norm | -20 | RX | 0x4800 | 15.4 |
|
||||
| 00:00:09.263 | src: [fdde:ad00:beef:0:0:ff:fe00:4800]:0 |
|
||||
| | dst: [ff03:0:0:0:0:0:0:2]:0 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 12 | 0x3f7d | yes | net | -20 | RX | 0x4800 | 15.4 |
|
||||
| 00:00:09.302 | src: [fdde:ad00:beef:0:0:ff:fe00:4800]:61631 |
|
||||
| | dst: [fdde:ad00:beef:0:0:ff:fe00:4801]:61631 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | ICMP6(EchoReqst) | 16 | 0x942c | yes | norm | -20 | RX | 0x4800 | 15.4 |
|
||||
| 00:00:09.304 | src: [fdde:ad00:beef:0:ac09:a16b:3204:dc09]:0 |
|
||||
| | dst: [fdde:ad00:beef:0:dc0e:d6b3:f180:b75b]:0 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | HopOpts | 44 | 0x0000 | yes | norm | -20 | RX | 0x4800 | 15.4 |
|
||||
| 00:00:09.304 | src: [fdde:ad00:beef:0:0:ff:fe00:4800]:0 |
|
||||
| | dst: [ff03:0:0:0:0:0:0:2]:0 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 50 | 0x2e37 | no | net | -20 | RX | 0x4800 | 15.4 |
|
||||
| 00:00:21.622 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788 |
|
||||
| | dst: [ff02:0:0:0:0:0:0:1]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 50 | 0xe177 | no | net | -20 | RX | 0x4800 | 15.4 |
|
||||
| 00:00:26.640 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788 |
|
||||
| | dst: [ff02:0:0:0:0:0:0:1]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 165 | 0x82ee | yes | net | -20 | RX | 0x4800 | 15.4 |
|
||||
| 00:00:30.000 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788 |
|
||||
| | dst: [fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 93 | 0x52df | no | net | -20 | RX | unknwn | 15.4 |
|
||||
| 00:00:30.480 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788 |
|
||||
| | dst: [fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 50 | 0x5ccf | no | net | -20 | RX | unknwn | 15.4 |
|
||||
| 00:00:30.772 | src: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788 |
|
||||
| | dst: [ff02:0:0:0:0:0:0:1]:19788 |
|
||||
Done
|
||||
|
||||
```
|
||||
|
||||
Print the latest 5 entries of the IPv6 message RX history as a list:
|
||||
|
||||
```bash
|
||||
> history rx list 4
|
||||
00:00:13.368
|
||||
type:UDP len:50 cheksum:0xbd26 sec:no prio:net rss:-20 from:0x4800 radio:15.4
|
||||
src:[fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788
|
||||
dst:[ff02:0:0:0:0:0:0:1]:19788
|
||||
00:00:14.991
|
||||
type:HopOpts len:44 cheksum:0x0000 sec:yes prio:norm rss:-20 from:0x4800 radio:15.4
|
||||
src:[fdde:ad00:beef:0:0:ff:fe00:4800]:0
|
||||
dst:[ff03:0:0:0:0:0:0:2]:0
|
||||
00:00:15.030
|
||||
type:UDP len:12 cheksum:0x3f7d sec:yes prio:net rss:-20 from:0x4800 radio:15.4
|
||||
src:[fdde:ad00:beef:0:0:ff:fe00:4800]:61631
|
||||
dst:[fdde:ad00:beef:0:0:ff:fe00:4801]:61631
|
||||
00:00:15.032
|
||||
type:ICMP6(EchoReqst) len:16 cheksum:0x942c sec:yes prio:norm rss:-20 from:0x4800 radio:15.4
|
||||
src:[fdde:ad00:beef:0:ac09:a16b:3204:dc09]:0
|
||||
dst:[fdde:ad00:beef:0:dc0e:d6b3:f180:b75b]:0
|
||||
Done
|
||||
```
|
||||
|
||||
### rxtx
|
||||
|
||||
Usage `history rxtx [list] [<num-entries>]`
|
||||
|
||||
Print the combined IPv6 message RX and TX history in either table or list format. Each entry provides:
|
||||
|
||||
- IPv6 message type: UDP, TCP, ICMP6 (and its subtype), etc.
|
||||
- IPv6 payload length (excludes the IPv6 header).
|
||||
- Source IPv6 address and port number.
|
||||
- Destination IPv6 address and port number (port number is valid for UDP/TCP, it is zero otherwise).
|
||||
- Whether or not link-layer security was used.
|
||||
- Message priority: low, norm, high, net (for Thread control messages).
|
||||
- Message checksum (valid for UDP, TCP, or ICMP6 message)
|
||||
- RSS: Received Signal Strength (in dBm) - averaged over all received fragment frames that formed the message. For TX history `NA` (not applicable) is used.
|
||||
- Whether the message was sent or received (`TX` or `RX`). A failed transmission (e.g., if tx was aborted or no ack from peer for any of the message fragments) is indicated with `TX-F` in the table format or `tx-success:no` in the list format.
|
||||
- Short address (RLOC16) of neighbor to/from which the message was sent/received. If the frame is broadcast, it is shown as `bcast` in table format or `0xffff` in the list format. If the short address of neighbor is not available, it is shown as `unknwn` in the table format or `0xfffe` in the list format.
|
||||
- Radio link on which the message was sent/received (useful when `OPENTHREAD_CONFIG_MULTI_RADIO` is enabled). Can be `15.4`, `trel`, or `all` (if sent on all radio links).
|
||||
|
||||
Print the IPv6 message RX and TX history as a table:
|
||||
|
||||
```bash
|
||||
> history rxtx
|
||||
| Age | Type | Len | Chksum | Sec | Prio | RSS |Dir | Neighb | Radio |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | HopOpts | 44 | 0x0000 | yes | norm | -20 | RX | 0x0800 | 15.4 |
|
||||
| 00:00:09.267 | src: [fdde:ad00:beef:0:0:ff:fe00:800]:0 |
|
||||
| | dst: [ff03:0:0:0:0:0:0:2]:0 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 12 | 0x6c6b | yes | net | -20 | RX | 0x0800 | 15.4 |
|
||||
| 00:00:09.290 | src: [fdde:ad00:beef:0:0:ff:fe00:800]:61631 |
|
||||
| | dst: [fdde:ad00:beef:0:0:ff:fe00:801]:61631 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | ICMP6(EchoReqst) | 16 | 0xc6a2 | yes | norm | -20 | RX | 0x0800 | 15.4 |
|
||||
| 00:00:09.292 | src: [fdde:ad00:beef:0:efe8:4910:cf95:dee9]:0 |
|
||||
| | dst: [fdde:ad00:beef:0:af4c:3644:882a:3698]:0 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | ICMP6(EchoReply) | 16 | 0xc5a2 | yes | norm | NA | TX | 0x0800 | 15.4 |
|
||||
| 00:00:09.292 | src: [fdde:ad00:beef:0:af4c:3644:882a:3698]:0 |
|
||||
| | dst: [fdde:ad00:beef:0:efe8:4910:cf95:dee9]:0 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 50 | 0xaa0d | yes | net | NA | TX | 0x0800 | 15.4 |
|
||||
| 00:00:09.294 | src: [fdde:ad00:beef:0:0:ff:fe00:801]:61631 |
|
||||
| | dst: [fdde:ad00:beef:0:0:ff:fe00:800]:61631 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | HopOpts | 44 | 0x0000 | yes | norm | -20 | RX | 0x0800 | 15.4 |
|
||||
| 00:00:09.296 | src: [fdde:ad00:beef:0:0:ff:fe00:800]:0 |
|
||||
| | dst: [ff03:0:0:0:0:0:0:2]:0 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 50 | 0xc1d8 | no | net | -20 | RX | 0x0800 | 15.4 |
|
||||
| 00:00:09.569 | src: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788 |
|
||||
| | dst: [ff02:0:0:0:0:0:0:1]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 50 | 0x3cb1 | no | net | -20 | RX | 0x0800 | 15.4 |
|
||||
| 00:00:16.519 | src: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788 |
|
||||
| | dst: [ff02:0:0:0:0:0:0:1]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 50 | 0xeda0 | no | net | -20 | RX | 0x0800 | 15.4 |
|
||||
| 00:00:20.599 | src: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788 |
|
||||
| | dst: [ff02:0:0:0:0:0:0:1]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 165 | 0xbdfa | yes | net | -20 | RX | 0x0800 | 15.4 |
|
||||
| 00:00:21.059 | src: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788 |
|
||||
| | dst: [fe80:0:0:0:8893:c2cc:d983:1e1c]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 64 | 0x1c11 | no | net | NA | TX | 0x0800 | 15.4 |
|
||||
| 00:00:21.062 | src: [fe80:0:0:0:8893:c2cc:d983:1e1c]:19788 |
|
||||
| | dst: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 93 | 0xedff | no | net | -20 | RX | unknwn | 15.4 |
|
||||
| 00:00:21.474 | src: [fe80:0:0:0:54d9:5153:ffc6:df26]:19788 |
|
||||
| | dst: [fe80:0:0:0:8893:c2cc:d983:1e1c]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 44 | 0xd383 | no | net | NA | TX | bcast | 15.4 |
|
||||
| 00:00:21.811 | src: [fe80:0:0:0:8893:c2cc:d983:1e1c]:19788 |
|
||||
| | dst: [ff02:0:0:0:0:0:0:2]:19788 |
|
||||
Done
|
||||
```
|
||||
|
||||
Print the latest 5 entries of the IPv6 message RX history as a list:
|
||||
|
||||
```bash
|
||||
> history rxtx list 5
|
||||
|
||||
00:00:02.100
|
||||
type:UDP len:50 cheksum:0xd843 sec:no prio:net rss:-20 from:0x0800 radio:15.4
|
||||
src:[fe80:0:0:0:54d9:5153:ffc6:df26]:19788
|
||||
dst:[ff02:0:0:0:0:0:0:1]:19788
|
||||
00:00:15.331
|
||||
type:HopOpts len:44 cheksum:0x0000 sec:yes prio:norm rss:-20 from:0x0800 radio:15.4
|
||||
src:[fdde:ad00:beef:0:0:ff:fe00:800]:0
|
||||
dst:[ff03:0:0:0:0:0:0:2]:0
|
||||
00:00:15.354
|
||||
type:UDP len:12 cheksum:0x6c6b sec:yes prio:net rss:-20 from:0x0800 radio:15.4
|
||||
src:[fdde:ad00:beef:0:0:ff:fe00:800]:61631
|
||||
dst:[fdde:ad00:beef:0:0:ff:fe00:801]:61631
|
||||
00:00:15.356
|
||||
type:ICMP6(EchoReqst) len:16 cheksum:0xc6a2 sec:yes prio:norm rss:-20 from:0x0800 radio:15.4
|
||||
src:[fdde:ad00:beef:0:efe8:4910:cf95:dee9]:0
|
||||
dst:[fdde:ad00:beef:0:af4c:3644:882a:3698]:0
|
||||
00:00:15.356
|
||||
type:ICMP6(EchoReply) len:16 cheksum:0xc5a2 sec:yes prio:norm tx-success:yes to:0x0800 radio:15.4
|
||||
src:[fdde:ad00:beef:0:af4c:3644:882a:3698]:0
|
||||
dst:[fdde:ad00:beef:0:efe8:4910:cf95:dee9]:0
|
||||
```
|
||||
|
||||
### tx
|
||||
|
||||
Usage `history tx [list] [<num-entries>]`
|
||||
|
||||
Print the IPv6 message TX history in either table or list format. Entries provide same information and follow same format as in `history rxtx` command.
|
||||
|
||||
Print the IPv6 message TX history as a table (10 latest entries):
|
||||
|
||||
```bash
|
||||
> history tx
|
||||
| Age | Type | Len | Chksum | Sec | Prio | RSS |Dir | Neighb | Radio |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | ICMP6(EchoReply) | 16 | 0x932c | yes | norm | NA | TX | 0x4800 | 15.4 |
|
||||
| 00:00:18.798 | src: [fdde:ad00:beef:0:dc0e:d6b3:f180:b75b]:0 |
|
||||
| | dst: [fdde:ad00:beef:0:ac09:a16b:3204:dc09]:0 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 50 | 0xce87 | yes | net | NA | TX | 0x4800 | 15.4 |
|
||||
| 00:00:18.800 | src: [fdde:ad00:beef:0:0:ff:fe00:4801]:61631 |
|
||||
| | dst: [fdde:ad00:beef:0:0:ff:fe00:4800]:61631 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 64 | 0xf7ba | no | net | NA | TX | 0x4800 | 15.4 |
|
||||
| 00:00:39.499 | src: [fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788 |
|
||||
| | dst: [fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788 |
|
||||
+----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
| | UDP | 44 | 0x26d4 | no | net | NA | TX | bcast | 15.4 |
|
||||
| 00:00:40.256 | src: [fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788 |
|
||||
| | dst: [ff02:0:0:0:0:0:0:2]:19788 |
|
||||
Done
|
||||
```
|
||||
|
||||
Print the IPv6 message TX history as a list:
|
||||
|
||||
```bash
|
||||
history tx list
|
||||
00:00:23.957
|
||||
type:ICMP6(EchoReply) len:16 cheksum:0x932c sec:yes prio:norm tx-success:yes to:0x4800 radio:15.4
|
||||
src:[fdde:ad00:beef:0:dc0e:d6b3:f180:b75b]:0
|
||||
dst:[fdde:ad00:beef:0:ac09:a16b:3204:dc09]:0
|
||||
00:00:23.959
|
||||
type:UDP len:50 cheksum:0xce87 sec:yes prio:net tx-success:yes to:0x4800 radio:15.4
|
||||
src:[fdde:ad00:beef:0:0:ff:fe00:4801]:61631
|
||||
dst:[fdde:ad00:beef:0:0:ff:fe00:4800]:61631
|
||||
00:00:44.658
|
||||
type:UDP len:64 cheksum:0xf7ba sec:no prio:net tx-success:yes to:0x4800 radio:15.4
|
||||
src:[fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788
|
||||
dst:[fe80:0:0:0:d03d:d3e7:cc5e:7cd7]:19788
|
||||
00:00:45.415
|
||||
type:UDP len:44 cheksum:0x26d4 sec:no prio:net tx-success:yes to:0xffff radio:15.4
|
||||
src:[fe80:0:0:0:a4a5:bbac:a8e:bd07]:19788
|
||||
dst:[ff02:0:0:0:0:0:0:2]:19788
|
||||
Done
|
||||
```
|
||||
+56
-83
@@ -128,6 +128,9 @@ Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, voi
|
||||
#if OPENTHREAD_CONFIG_SRP_SERVER_ENABLE
|
||||
, mSrpServer(*this)
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
, mHistory(*this)
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
|
||||
, mOutputLength(0)
|
||||
, mIsLogging(false)
|
||||
@@ -162,6 +165,35 @@ void Interpreter::OutputBytes(const uint8_t *aBytes, uint16_t aLength)
|
||||
}
|
||||
}
|
||||
|
||||
const char *Interpreter::LinkModeToString(const otLinkModeConfig &aLinkMode, char (&aStringBuffer)[kLinkModeStringSize])
|
||||
{
|
||||
char *flagsPtr = &aStringBuffer[0];
|
||||
|
||||
if (aLinkMode.mRxOnWhenIdle)
|
||||
{
|
||||
*flagsPtr++ = 'r';
|
||||
}
|
||||
|
||||
if (aLinkMode.mDeviceType)
|
||||
{
|
||||
*flagsPtr++ = 'd';
|
||||
}
|
||||
|
||||
if (aLinkMode.mNetworkData)
|
||||
{
|
||||
*flagsPtr++ = 'n';
|
||||
}
|
||||
|
||||
if (flagsPtr == &aStringBuffer[0])
|
||||
{
|
||||
*flagsPtr++ = '-';
|
||||
}
|
||||
|
||||
*flagsPtr = '\0';
|
||||
|
||||
return aStringBuffer;
|
||||
}
|
||||
|
||||
void Interpreter::OutputEnabledDisabledStatus(bool aEnabled)
|
||||
{
|
||||
OutputLine(aEnabled ? "Enabled" : "Disabled");
|
||||
@@ -202,7 +234,11 @@ void Interpreter::OutputTableHeader(uint8_t aNumColumns, const char *const aTitl
|
||||
}
|
||||
|
||||
OutputLine("|");
|
||||
OutputTableSeperator(aNumColumns, aWidths);
|
||||
}
|
||||
|
||||
void Interpreter::OutputTableSeperator(uint8_t aNumColumns, const uint8_t aWidths[])
|
||||
{
|
||||
for (uint8_t index = 0; index < aNumColumns; index++)
|
||||
{
|
||||
OutputFormat("+");
|
||||
@@ -324,6 +360,13 @@ otError Interpreter::ProcessHelp(Arg aArgs[])
|
||||
return OT_ERROR_NONE;
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
otError Interpreter::ProcessHistory(Arg aArgs[])
|
||||
{
|
||||
return mHistory.Process(aArgs);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_BORDER_AGENT_ENABLE
|
||||
otError Interpreter::ProcessBorderAgent(Arg aArgs[])
|
||||
{
|
||||
@@ -866,10 +909,12 @@ exit:
|
||||
#if OPENTHREAD_FTD
|
||||
otError Interpreter::ProcessChild(Arg aArgs[])
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
otChildInfo childInfo;
|
||||
uint16_t childId;
|
||||
bool isTable;
|
||||
otError error = OT_ERROR_NONE;
|
||||
otChildInfo childInfo;
|
||||
uint16_t childId;
|
||||
bool isTable;
|
||||
otLinkModeConfig linkMode;
|
||||
char linkModeString[kLinkModeStringSize];
|
||||
|
||||
isTable = (aArgs[0] == "table");
|
||||
|
||||
@@ -936,32 +981,10 @@ otError Interpreter::ProcessChild(Arg aArgs[])
|
||||
OutputFormat("Ext Addr: ");
|
||||
OutputExtAddress(childInfo.mExtAddress);
|
||||
OutputLine("");
|
||||
OutputFormat("Mode: ");
|
||||
|
||||
if (!(childInfo.mRxOnWhenIdle || childInfo.mFullThreadDevice || childInfo.mFullNetworkData))
|
||||
{
|
||||
OutputFormat("-");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (childInfo.mRxOnWhenIdle)
|
||||
{
|
||||
OutputFormat("r");
|
||||
}
|
||||
|
||||
if (childInfo.mFullThreadDevice)
|
||||
{
|
||||
OutputFormat("d");
|
||||
}
|
||||
|
||||
if (childInfo.mFullNetworkData)
|
||||
{
|
||||
OutputFormat("n");
|
||||
}
|
||||
}
|
||||
|
||||
OutputLine("");
|
||||
|
||||
linkMode.mRxOnWhenIdle = childInfo.mRxOnWhenIdle;
|
||||
linkMode.mDeviceType = childInfo.mFullThreadDevice;
|
||||
linkMode.mNetworkData = childInfo.mFullThreadDevice;
|
||||
OutputLine("Mode: %s", LinkModeToString(linkMode, linkModeString));
|
||||
OutputLine("Net Data: %d", childInfo.mNetworkDataVersion);
|
||||
OutputLine("Timeout: %d", childInfo.mTimeout);
|
||||
OutputLine("Age: %d", childInfo.mAge);
|
||||
@@ -2663,32 +2686,9 @@ otError Interpreter::ProcessMode(Arg aArgs[])
|
||||
|
||||
if (aArgs[0].IsEmpty())
|
||||
{
|
||||
linkMode = otThreadGetLinkMode(mInstance);
|
||||
|
||||
if (!(linkMode.mRxOnWhenIdle || linkMode.mDeviceType || linkMode.mNetworkData))
|
||||
{
|
||||
OutputFormat("-");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (linkMode.mRxOnWhenIdle)
|
||||
{
|
||||
OutputFormat("r");
|
||||
}
|
||||
|
||||
if (linkMode.mDeviceType)
|
||||
{
|
||||
OutputFormat("d");
|
||||
}
|
||||
|
||||
if (linkMode.mNetworkData)
|
||||
{
|
||||
OutputFormat("n");
|
||||
}
|
||||
}
|
||||
|
||||
OutputLine("");
|
||||
char linkModeString[kLinkModeStringSize];
|
||||
|
||||
OutputLine("%s", LinkModeToString(otThreadGetLinkMode(mInstance), linkModeString));
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
@@ -4026,34 +4026,7 @@ otError Interpreter::ProcessState(Arg aArgs[])
|
||||
|
||||
if (aArgs[0].IsEmpty())
|
||||
{
|
||||
switch (otThreadGetDeviceRole(mInstance))
|
||||
{
|
||||
case OT_DEVICE_ROLE_DISABLED:
|
||||
OutputLine("disabled");
|
||||
break;
|
||||
|
||||
case OT_DEVICE_ROLE_DETACHED:
|
||||
OutputLine("detached");
|
||||
break;
|
||||
|
||||
case OT_DEVICE_ROLE_CHILD:
|
||||
OutputLine("child");
|
||||
break;
|
||||
|
||||
#if OPENTHREAD_FTD
|
||||
case OT_DEVICE_ROLE_ROUTER:
|
||||
OutputLine("router");
|
||||
break;
|
||||
|
||||
case OT_DEVICE_ROLE_LEADER:
|
||||
OutputLine("leader");
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
OutputLine("invalid state");
|
||||
break;
|
||||
}
|
||||
OutputLine("%s", otThreadDeviceRoleToString(otThreadGetDeviceRole(mInstance)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
|
||||
#include "cli/cli_commissioner.hpp"
|
||||
#include "cli/cli_dataset.hpp"
|
||||
#include "cli/cli_history.hpp"
|
||||
#include "cli/cli_joiner.hpp"
|
||||
#include "cli/cli_network_data.hpp"
|
||||
#include "cli/cli_srp_client.hpp"
|
||||
@@ -100,6 +101,7 @@ class Interpreter
|
||||
friend class CoapSecure;
|
||||
friend class Commissioner;
|
||||
friend class Dataset;
|
||||
friend class History;
|
||||
friend class Joiner;
|
||||
friend class NetworkData;
|
||||
friend class SrpClient;
|
||||
@@ -306,6 +308,22 @@ public:
|
||||
*/
|
||||
void SetUserCommands(const otCliCommand *aCommands, uint8_t aLength, void *aContext);
|
||||
|
||||
static constexpr uint8_t kLinkModeStringSize = sizeof("rdn"); ///< Size of string buffer for a MLE Link Mode.
|
||||
|
||||
/**
|
||||
* This method converts a given MLE Link Mode to flag string.
|
||||
*
|
||||
* The characters 'r', 'd', and 'n' are respectively used for `mRxOnWhenIdle`, `mDeviceType` and `mNetworkData`
|
||||
* flags. If all flags are `false`, then "-" is returned.
|
||||
*
|
||||
* @param[in] aLinkMode The MLE Link Mode to convert.
|
||||
* @param[out] aStringBuffer A reference to an string array to place the string.
|
||||
*
|
||||
* @returns A pointer @p aStringBuffer which contains the converted string.
|
||||
*
|
||||
*/
|
||||
static const char *LinkModeToString(const otLinkModeConfig &aLinkMode, char (&aStringBuffer)[kLinkModeStringSize]);
|
||||
|
||||
protected:
|
||||
static Interpreter *sInterpreter;
|
||||
|
||||
@@ -401,6 +419,7 @@ private:
|
||||
}
|
||||
|
||||
void OutputTableHeader(uint8_t aNumColumns, const char *const aTitles[], const uint8_t aWidths[]);
|
||||
void OutputTableSeperator(uint8_t aNumColumns, const uint8_t aWidths[]);
|
||||
|
||||
template <uint8_t kTableNumColumns>
|
||||
void OutputTableHeader(const char *const (&aTitles)[kTableNumColumns], const uint8_t (&aWidths)[kTableNumColumns])
|
||||
@@ -408,6 +427,11 @@ private:
|
||||
OutputTableHeader(kTableNumColumns, &aTitles[0], aWidths);
|
||||
}
|
||||
|
||||
template <uint8_t kTableNumColumns> void OutputTableSeperator(const uint8_t (&aWidths)[kTableNumColumns])
|
||||
{
|
||||
OutputTableSeperator(kTableNumColumns, aWidths);
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_PING_SENDER_ENABLE
|
||||
otError ParsePingInterval(const Arg &aArg, uint32_t &aInterval);
|
||||
#endif
|
||||
@@ -419,6 +443,7 @@ private:
|
||||
|
||||
otError ProcessUserCommands(Arg aArgs[]);
|
||||
otError ProcessHelp(Arg aArgs[]);
|
||||
otError ProcessHistory(Arg aArgs[]);
|
||||
otError ProcessCcaThreshold(Arg aArgs[]);
|
||||
otError ProcessBufferInfo(Arg aArgs[]);
|
||||
otError ProcessChannel(Arg aArgs[]);
|
||||
@@ -804,6 +829,9 @@ private:
|
||||
#endif
|
||||
{"fem", &Interpreter::ProcessFem},
|
||||
{"help", &Interpreter::ProcessHelp},
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
{"history", &Interpreter::ProcessHistory},
|
||||
#endif
|
||||
{"ifconfig", &Interpreter::ProcessIfconfig},
|
||||
{"ipaddr", &Interpreter::ProcessIpAddr},
|
||||
{"ipmaddr", &Interpreter::ProcessIpMulticastAddr},
|
||||
@@ -953,6 +981,10 @@ private:
|
||||
SrpServer mSrpServer;
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
History mHistory;
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_ENABLE
|
||||
char mOutputString[OPENTHREAD_CONFIG_CLI_LOG_INPUT_OUTPUT_LOG_STRING_SIZE];
|
||||
uint16_t mOutputLength;
|
||||
|
||||
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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 CLI for the History Tracker.
|
||||
*/
|
||||
|
||||
#include "cli_history.hpp"
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "cli/cli.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace Cli {
|
||||
|
||||
constexpr History::Command History::sCommands[];
|
||||
|
||||
otError History::ProcessHelp(Arg aArgs[])
|
||||
{
|
||||
OT_UNUSED_VARIABLE(aArgs);
|
||||
|
||||
for (const Command &command : sCommands)
|
||||
{
|
||||
mInterpreter.OutputLine(command.mName);
|
||||
}
|
||||
|
||||
return OT_ERROR_NONE;
|
||||
}
|
||||
|
||||
otError History::ParseArgs(Arg aArgs[], bool &aIsList, uint16_t &aNumEntries) const
|
||||
{
|
||||
if (*aArgs == "list")
|
||||
{
|
||||
aArgs++;
|
||||
aIsList = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
aIsList = false;
|
||||
}
|
||||
|
||||
if (aArgs->ParseAsUint16(aNumEntries) == OT_ERROR_NONE)
|
||||
{
|
||||
aArgs++;
|
||||
}
|
||||
else
|
||||
{
|
||||
aNumEntries = 0;
|
||||
}
|
||||
|
||||
return aArgs[0].IsEmpty() ? OT_ERROR_NONE : OT_ERROR_INVALID_ARGS;
|
||||
}
|
||||
|
||||
otError History::ProcessNetInfo(Arg aArgs[])
|
||||
{
|
||||
otError error;
|
||||
bool isList;
|
||||
uint16_t numEntries;
|
||||
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};
|
||||
|
||||
mInterpreter.OutputTableHeader(kNetInfoTitles, kNetInfoColumnWidths);
|
||||
}
|
||||
|
||||
otHistoryTrackerInitIterator(&iterator);
|
||||
|
||||
for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
|
||||
{
|
||||
info = otHistoryTrackerIterateNetInfoHistory(mInterpreter.mInstance, &iterator, &entryAge);
|
||||
VerifyOrExit(info != nullptr);
|
||||
|
||||
otHistoryTrackerEntryAgeToString(entryAge, ageString, sizeof(ageString));
|
||||
|
||||
mInterpreter.OutputLine(
|
||||
isList ? "%s -> role:%s mode:%s rloc16:0x%04x partition-id:%u" : "| %20s | %-8s | %-4s | 0x%04x | %12u |",
|
||||
ageString, otThreadDeviceRoleToString(info->mRole),
|
||||
Interpreter::LinkModeToString(info->mMode, linkModeString), info->mRloc16, info->mPartitionId);
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
otError History::ProcessRx(Arg aArgs[])
|
||||
{
|
||||
return ProcessRxTxHistory(kRx, aArgs);
|
||||
}
|
||||
|
||||
otError History::ProcessRxTx(Arg aArgs[])
|
||||
{
|
||||
return ProcessRxTxHistory(kRxTx, aArgs);
|
||||
}
|
||||
|
||||
otError History::ProcessTx(Arg aArgs[])
|
||||
{
|
||||
return ProcessRxTxHistory(kTx, aArgs);
|
||||
}
|
||||
|
||||
const char *History::MessagePriorityToString(uint8_t aPriority)
|
||||
{
|
||||
const char *str = "unkn";
|
||||
|
||||
switch (aPriority)
|
||||
{
|
||||
case OT_HISTORY_TRACKER_MSG_PRIORITY_LOW:
|
||||
str = "low";
|
||||
break;
|
||||
|
||||
case OT_HISTORY_TRACKER_MSG_PRIORITY_NORMAL:
|
||||
str = "norm";
|
||||
break;
|
||||
|
||||
case OT_HISTORY_TRACKER_MSG_PRIORITY_HIGH:
|
||||
str = "high";
|
||||
break;
|
||||
|
||||
case OT_HISTORY_TRACKER_MSG_PRIORITY_NET:
|
||||
str = "net";
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
const char *History::RadioTypeToString(const otHistoryTrackerMessageInfo &aInfo)
|
||||
{
|
||||
const char *str = "none";
|
||||
|
||||
if (aInfo.mRadioTrelUdp6 && aInfo.mRadioIeee802154)
|
||||
{
|
||||
str = "all";
|
||||
}
|
||||
else if (aInfo.mRadioIeee802154)
|
||||
{
|
||||
str = "15.4";
|
||||
}
|
||||
else if (aInfo.mRadioTrelUdp6)
|
||||
{
|
||||
str = "trel";
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
const char *History::MessageTypeToString(const otHistoryTrackerMessageInfo &aInfo)
|
||||
{
|
||||
const char *str = otIp6ProtoToString(aInfo.mIpProto);
|
||||
|
||||
if (aInfo.mIpProto == OT_IP6_PROTO_ICMP6)
|
||||
{
|
||||
switch (aInfo.mIcmp6Type)
|
||||
{
|
||||
case OT_ICMP6_TYPE_DST_UNREACH:
|
||||
str = "ICMP6(Unreach)";
|
||||
break;
|
||||
case OT_ICMP6_TYPE_PACKET_TO_BIG:
|
||||
str = "ICMP6(TooBig)";
|
||||
break;
|
||||
case OT_ICMP6_TYPE_ECHO_REQUEST:
|
||||
str = "ICMP6(EchoReqst)";
|
||||
break;
|
||||
case OT_ICMP6_TYPE_ECHO_REPLY:
|
||||
str = "ICMP6(EchoReply)";
|
||||
break;
|
||||
case OT_ICMP6_TYPE_ROUTER_SOLICIT:
|
||||
str = "ICMP6(RouterSol)";
|
||||
break;
|
||||
case OT_ICMP6_TYPE_ROUTER_ADVERT:
|
||||
str = "ICMP6(RouterAdv)";
|
||||
break;
|
||||
default:
|
||||
str = "ICMP6(Other)";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
otError History::ProcessRxTxHistory(RxTx aRxTx, Arg aArgs[])
|
||||
{
|
||||
otError error;
|
||||
bool isList;
|
||||
uint16_t numEntries;
|
||||
otHistoryTrackerIterator rxIterator;
|
||||
otHistoryTrackerIterator txIterator;
|
||||
bool isRx = false;
|
||||
const otHistoryTrackerMessageInfo *info = nullptr;
|
||||
const otHistoryTrackerMessageInfo *rxInfo = nullptr;
|
||||
const otHistoryTrackerMessageInfo *txInfo = nullptr;
|
||||
uint32_t entryAge;
|
||||
uint32_t rxEntryAge;
|
||||
uint32_t txEntryAge;
|
||||
|
||||
// | Age | Type | Len | Chksum | Sec | Prio | RSS |Dir | Neighb | Radio |
|
||||
// +----------------------+------------------+-------+--------+-----+------+------+----+--------+-------+
|
||||
|
||||
static const char *const kTableTitles[] = {"Age", "Type", "Len", "Chksum", "Sec",
|
||||
"Prio", "RSS", "Dir", "Neighb", "Radio"};
|
||||
|
||||
static const uint8_t kTableColumnWidths[] = {22, 18, 7, 8, 5, 6, 6, 4, 8, 7};
|
||||
|
||||
SuccessOrExit(error = ParseArgs(aArgs, isList, numEntries));
|
||||
|
||||
if (!isList)
|
||||
{
|
||||
mInterpreter.OutputTableHeader(kTableTitles, kTableColumnWidths);
|
||||
}
|
||||
|
||||
otHistoryTrackerInitIterator(&txIterator);
|
||||
otHistoryTrackerInitIterator(&rxIterator);
|
||||
|
||||
for (uint16_t index = 0; (numEntries == 0) || (index < numEntries); index++)
|
||||
{
|
||||
switch (aRxTx)
|
||||
{
|
||||
case kRx:
|
||||
info = otHistoryTrackerIterateRxHistory(mInterpreter.mInstance, &rxIterator, &entryAge);
|
||||
isRx = true;
|
||||
break;
|
||||
|
||||
case kTx:
|
||||
info = otHistoryTrackerIterateTxHistory(mInterpreter.mInstance, &txIterator, &entryAge);
|
||||
isRx = false;
|
||||
break;
|
||||
|
||||
case kRxTx:
|
||||
// Iterate through both RX and TX lists and determine the entry
|
||||
// with earlier age.
|
||||
|
||||
if (rxInfo == nullptr)
|
||||
{
|
||||
rxInfo = otHistoryTrackerIterateRxHistory(mInterpreter.mInstance, &rxIterator, &rxEntryAge);
|
||||
}
|
||||
|
||||
if (txInfo == nullptr)
|
||||
{
|
||||
txInfo = otHistoryTrackerIterateTxHistory(mInterpreter.mInstance, &txIterator, &txEntryAge);
|
||||
}
|
||||
|
||||
if ((rxInfo != nullptr) && ((txInfo == nullptr) || (rxEntryAge <= txEntryAge)))
|
||||
{
|
||||
info = rxInfo;
|
||||
entryAge = rxEntryAge;
|
||||
isRx = true;
|
||||
rxInfo = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
info = txInfo;
|
||||
entryAge = txEntryAge;
|
||||
isRx = false;
|
||||
txInfo = nullptr;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
VerifyOrExit(info != nullptr);
|
||||
|
||||
if (isList)
|
||||
{
|
||||
OutputRxTxEntryListFormat(*info, entryAge, isRx);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (index != 0)
|
||||
{
|
||||
mInterpreter.OutputTableSeperator(kTableColumnWidths);
|
||||
}
|
||||
|
||||
OutputRxTxEntryTableFormat(*info, entryAge, isRx);
|
||||
}
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
void History::OutputRxTxEntryListFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx)
|
||||
{
|
||||
constexpr uint8_t kIndentSize = 4;
|
||||
|
||||
char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
|
||||
char addrString[OT_IP6_SOCK_ADDR_STRING_SIZE];
|
||||
|
||||
otHistoryTrackerEntryAgeToString(aEntryAge, ageString, sizeof(ageString));
|
||||
|
||||
mInterpreter.OutputLine("%s", ageString);
|
||||
mInterpreter.OutputFormat(kIndentSize, "type:%s len:%u cheksum:0x%04x sec:%s prio:%s ", MessageTypeToString(aInfo),
|
||||
aInfo.mPayloadLength, aInfo.mChecksum, aInfo.mLinkSecurity ? "yes" : "no",
|
||||
MessagePriorityToString(aInfo.mPriority));
|
||||
if (aIsRx)
|
||||
{
|
||||
mInterpreter.OutputFormat("rss:%d", aInfo.mAveRxRss);
|
||||
}
|
||||
else
|
||||
{
|
||||
mInterpreter.OutputFormat("tx-success:%s", aInfo.mTxSuccess ? "yes" : "no");
|
||||
}
|
||||
|
||||
mInterpreter.OutputLine(" %s:0x%04x radio:%s", aIsRx ? "from" : "to", aInfo.mNeighborRloc16,
|
||||
RadioTypeToString(aInfo));
|
||||
|
||||
otIp6SockAddrToString(&aInfo.mSource, addrString, sizeof(addrString));
|
||||
mInterpreter.OutputLine(kIndentSize, "src:%s", addrString);
|
||||
|
||||
otIp6SockAddrToString(&aInfo.mDestination, addrString, sizeof(addrString));
|
||||
mInterpreter.OutputLine(kIndentSize, "dst:%s", addrString);
|
||||
}
|
||||
|
||||
void History::OutputRxTxEntryTableFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx)
|
||||
{
|
||||
char ageString[OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE];
|
||||
char addrString[OT_IP6_SOCK_ADDR_STRING_SIZE];
|
||||
|
||||
otHistoryTrackerEntryAgeToString(aEntryAge, ageString, sizeof(ageString));
|
||||
|
||||
mInterpreter.OutputFormat("| %20s | %-16.16s | %5u | 0x%04x | %3s | %4s | ", "", MessageTypeToString(aInfo),
|
||||
aInfo.mPayloadLength, aInfo.mChecksum, aInfo.mLinkSecurity ? "yes" : "no",
|
||||
MessagePriorityToString(aInfo.mPriority));
|
||||
|
||||
if (aIsRx)
|
||||
{
|
||||
mInterpreter.OutputFormat("%4d | RX ", aInfo.mAveRxRss);
|
||||
}
|
||||
else
|
||||
{
|
||||
mInterpreter.OutputFormat(" NA |");
|
||||
mInterpreter.OutputFormat(aInfo.mTxSuccess ? " TX " : "TX-F");
|
||||
}
|
||||
|
||||
if (aInfo.mNeighborRloc16 == kShortAddrBroadcast)
|
||||
{
|
||||
mInterpreter.OutputFormat("| bcast ");
|
||||
}
|
||||
else if (aInfo.mNeighborRloc16 == kShortAddrInvalid)
|
||||
{
|
||||
mInterpreter.OutputFormat("| unknwn ");
|
||||
}
|
||||
else
|
||||
{
|
||||
mInterpreter.OutputFormat("| 0x%04x ", aInfo.mNeighborRloc16);
|
||||
}
|
||||
|
||||
mInterpreter.OutputLine("| %5.5s |", RadioTypeToString(aInfo));
|
||||
|
||||
otIp6SockAddrToString(&aInfo.mSource, addrString, sizeof(addrString));
|
||||
mInterpreter.OutputLine("| %20s | src: %-70s |", ageString, addrString);
|
||||
|
||||
otIp6SockAddrToString(&aInfo.mDestination, addrString, sizeof(addrString));
|
||||
mInterpreter.OutputLine("| %20s | dst: %-70s |", "", addrString);
|
||||
}
|
||||
|
||||
otError History::Process(Arg aArgs[])
|
||||
{
|
||||
otError error = OT_ERROR_INVALID_COMMAND;
|
||||
const Command *command;
|
||||
|
||||
if (aArgs[0].IsEmpty())
|
||||
{
|
||||
IgnoreError(ProcessHelp(aArgs));
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
command = Utils::LookupTable::Find(aArgs[0].GetCString(), sCommands);
|
||||
VerifyOrExit(command != nullptr);
|
||||
|
||||
error = (this->*command->mHandler)(aArgs + 1);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
} // namespace Cli
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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 contains definitions for CLI to control History Tracker
|
||||
*/
|
||||
|
||||
#ifndef CLI_HISTORY_HPP_
|
||||
#define CLI_HISTORY_HPP_
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#include <openthread/history_tracker.h>
|
||||
|
||||
#include "cli/cli_config.h"
|
||||
#include "utils/lookup_table.hpp"
|
||||
#include "utils/parse_cmdline.hpp"
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
|
||||
namespace ot {
|
||||
namespace Cli {
|
||||
|
||||
class Interpreter;
|
||||
|
||||
/**
|
||||
* This class implements the History Tracker CLI interpreter.
|
||||
*
|
||||
*/
|
||||
class History
|
||||
{
|
||||
public:
|
||||
typedef Utils::CmdLineParser::Arg Arg;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param[in] aInterpreter The CLI interpreter.
|
||||
*
|
||||
*/
|
||||
explicit History(Interpreter &aInterpreter)
|
||||
: mInterpreter(aInterpreter)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* This method interprets a list of CLI arguments.
|
||||
*
|
||||
* @param[in] aArgs A pointer an array of command line arguments.
|
||||
*
|
||||
*/
|
||||
otError Process(Arg aArgs[]);
|
||||
|
||||
private:
|
||||
static constexpr uint16_t kShortAddrInvalid = 0xfffe;
|
||||
static constexpr uint16_t kShortAddrBroadcast = 0xffff;
|
||||
static constexpr int8_t kInvalidRss = OT_RADIO_RSSI_INVALID;
|
||||
|
||||
struct Command
|
||||
{
|
||||
const char *mName;
|
||||
otError (History::*mHandler)(Arg aArgs[]);
|
||||
};
|
||||
|
||||
enum RxTx : uint8_t
|
||||
{
|
||||
kRx,
|
||||
kTx,
|
||||
kRxTx,
|
||||
};
|
||||
|
||||
otError ProcessHelp(Arg aArgs[]);
|
||||
otError ProcessNetInfo(Arg aArgs[]);
|
||||
otError ProcessRx(Arg aArgs[]);
|
||||
otError ProcessRxTx(Arg aArgs[]);
|
||||
otError ProcessTx(Arg aArgs[]);
|
||||
|
||||
otError ParseArgs(Arg aArgs[], bool &aIsList, uint16_t &aNumEntries) const;
|
||||
otError ProcessRxTxHistory(RxTx aRxTx, Arg aArgs[]);
|
||||
void OutputRxTxEntryListFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx);
|
||||
void OutputRxTxEntryTableFormat(const otHistoryTrackerMessageInfo &aInfo, uint32_t aEntryAge, bool aIsRx);
|
||||
|
||||
static const char *MessagePriorityToString(uint8_t aPriority);
|
||||
static const char *RadioTypeToString(const otHistoryTrackerMessageInfo &aInfo);
|
||||
static const char *MessageTypeToString(const otHistoryTrackerMessageInfo &aInfo);
|
||||
|
||||
static constexpr Command sCommands[] = {
|
||||
{"help", &History::ProcessHelp}, {"netinfo", &History::ProcessNetInfo}, {"rx", &History::ProcessRx},
|
||||
{"rxtx", &History::ProcessRxTx}, {"tx", &History::ProcessTx},
|
||||
};
|
||||
|
||||
static_assert(Utils::LookupTable::IsSorted(sCommands), "Command Table is not sorted");
|
||||
|
||||
Interpreter &mInterpreter;
|
||||
};
|
||||
|
||||
} // namespace Cli
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
|
||||
#endif // CLI_HISTORY_HPP_
|
||||
@@ -315,6 +315,7 @@ openthread_core_files = [
|
||||
"api/entropy_api.cpp",
|
||||
"api/error_api.cpp",
|
||||
"api/heap_api.cpp",
|
||||
"api/history_tracker_api.cpp",
|
||||
"api/icmp6_api.cpp",
|
||||
"api/instance_api.cpp",
|
||||
"api/ip6_api.cpp",
|
||||
@@ -638,6 +639,8 @@ openthread_core_files = [
|
||||
"utils/flash.hpp",
|
||||
"utils/heap.cpp",
|
||||
"utils/heap.hpp",
|
||||
"utils/history_tracker.cpp",
|
||||
"utils/history_tracker.hpp",
|
||||
"utils/jam_detector.cpp",
|
||||
"utils/jam_detector.hpp",
|
||||
"utils/lookup_table.cpp",
|
||||
@@ -710,6 +713,7 @@ source_set("libopenthread_core_config") {
|
||||
"config/dns_client.h",
|
||||
"config/dnssd_server.h",
|
||||
"config/dtls.h",
|
||||
"config/history_tracker.h",
|
||||
"config/ip6.h",
|
||||
"config/joiner.h",
|
||||
"config/link_quality.h",
|
||||
|
||||
@@ -51,6 +51,7 @@ set(COMMON_SOURCES
|
||||
api/entropy_api.cpp
|
||||
api/error_api.cpp
|
||||
api/heap_api.cpp
|
||||
api/history_tracker_api.cpp
|
||||
api/icmp6_api.cpp
|
||||
api/instance_api.cpp
|
||||
api/ip6_api.cpp
|
||||
@@ -215,6 +216,7 @@ set(COMMON_SOURCES
|
||||
utils/child_supervision.cpp
|
||||
utils/flash.cpp
|
||||
utils/heap.cpp
|
||||
utils/history_tracker.cpp
|
||||
utils/jam_detector.cpp
|
||||
utils/lookup_table.cpp
|
||||
utils/otns.cpp
|
||||
|
||||
@@ -128,6 +128,7 @@ SOURCES_COMMON = \
|
||||
api/entropy_api.cpp \
|
||||
api/error_api.cpp \
|
||||
api/heap_api.cpp \
|
||||
api/history_tracker_api.cpp \
|
||||
api/icmp6_api.cpp \
|
||||
api/instance_api.cpp \
|
||||
api/ip6_api.cpp \
|
||||
@@ -292,6 +293,7 @@ SOURCES_COMMON = \
|
||||
utils/child_supervision.cpp \
|
||||
utils/flash.cpp \
|
||||
utils/heap.cpp \
|
||||
utils/history_tracker.cpp \
|
||||
utils/jam_detector.cpp \
|
||||
utils/lookup_table.cpp \
|
||||
utils/otns.cpp \
|
||||
@@ -430,6 +432,7 @@ HEADERS_COMMON = \
|
||||
config/dns_client.h \
|
||||
config/dnssd_server.h \
|
||||
config/dtls.h \
|
||||
config/history_tracker.h \
|
||||
config/ip6.h \
|
||||
config/joiner.h \
|
||||
config/link_quality.h \
|
||||
@@ -562,6 +565,7 @@ HEADERS_COMMON = \
|
||||
utils/child_supervision.hpp \
|
||||
utils/flash.hpp \
|
||||
utils/heap.hpp \
|
||||
utils/history_tracker.hpp \
|
||||
utils/jam_detector.hpp \
|
||||
utils/lookup_table.hpp \
|
||||
utils/otns.hpp \
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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 public APIs.
|
||||
*/
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
|
||||
#include <openthread/history_tracker.h>
|
||||
|
||||
#include "common/instance.hpp"
|
||||
#include "common/locator_getters.hpp"
|
||||
#include "utils/history_tracker.hpp"
|
||||
|
||||
using namespace ot;
|
||||
|
||||
void otHistoryTrackerInitIterator(otHistoryTrackerIterator *aIterator)
|
||||
{
|
||||
static_cast<Utils::HistoryTracker::Iterator *>(aIterator)->Init();
|
||||
}
|
||||
|
||||
const otHistoryTrackerNetworkInfo *otHistoryTrackerIterateNetInfoHistory(otInstance * aInstance,
|
||||
otHistoryTrackerIterator *aIterator,
|
||||
uint32_t * aEntryAge)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.Get<Utils::HistoryTracker>().IterateNetInfoHistory(
|
||||
*static_cast<Utils::HistoryTracker::Iterator *>(aIterator), *aEntryAge);
|
||||
}
|
||||
|
||||
const otHistoryTrackerMessageInfo *otHistoryTrackerIterateRxHistory(otInstance * aInstance,
|
||||
otHistoryTrackerIterator *aIterator,
|
||||
uint32_t * aEntryAge)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.Get<Utils::HistoryTracker>().IterateRxHistory(
|
||||
*static_cast<Utils::HistoryTracker::Iterator *>(aIterator), *aEntryAge);
|
||||
}
|
||||
|
||||
const otHistoryTrackerMessageInfo *otHistoryTrackerIterateTxHistory(otInstance * aInstance,
|
||||
otHistoryTrackerIterator *aIterator,
|
||||
uint32_t * aEntryAge)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
return instance.Get<Utils::HistoryTracker>().IterateTxHistory(
|
||||
*static_cast<Utils::HistoryTracker::Iterator *>(aIterator), *aEntryAge);
|
||||
}
|
||||
|
||||
void otHistoryTrackerEntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize)
|
||||
{
|
||||
Utils::HistoryTracker::EntryAgeToString(aEntryAge, aBuffer, aSize);
|
||||
}
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
@@ -333,3 +333,8 @@ otError otIp6SetMeshLocalIid(otInstance *aInstance, const otIp6InterfaceIdentifi
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const char *otIp6ProtoToString(uint8_t aIpProto)
|
||||
{
|
||||
return Ip6::Ip6::IpProtoToString(aIpProto);
|
||||
}
|
||||
|
||||
@@ -336,6 +336,11 @@ otDeviceRole otThreadGetDeviceRole(otInstance *aInstance)
|
||||
return static_cast<otDeviceRole>(instance.Get<Mle::MleRouter>().GetRole());
|
||||
}
|
||||
|
||||
const char *otThreadDeviceRoleToString(otDeviceRole aRole)
|
||||
{
|
||||
return Mle::Mle::RoleToString(static_cast<Mle::DeviceRole>(aRole));
|
||||
}
|
||||
|
||||
otError otThreadGetLeaderData(otInstance *aInstance, otLeaderData *aLeaderData)
|
||||
{
|
||||
Instance &instance = *static_cast<Instance *>(aInstance);
|
||||
|
||||
@@ -87,6 +87,9 @@ Instance::Instance(void)
|
||||
#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD
|
||||
, mChannelManager(*this)
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
, mHistoryTracker(*this)
|
||||
#endif
|
||||
#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
|
||||
, mDatasetUpdater(*this)
|
||||
#endif
|
||||
|
||||
@@ -82,6 +82,9 @@
|
||||
#if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
|
||||
#include "utils/channel_monitor.hpp"
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
#include "utils/history_tracker.hpp"
|
||||
#endif
|
||||
|
||||
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
|
||||
#include "backbone_router/bbr_leader.hpp"
|
||||
@@ -388,6 +391,10 @@ private:
|
||||
Utils::ChannelManager mChannelManager;
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Utils::HistoryTracker mHistoryTracker;
|
||||
#endif
|
||||
|
||||
#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
|
||||
MeshCoP::DatasetUpdater mDatasetUpdater;
|
||||
#endif
|
||||
@@ -828,6 +835,13 @@ template <> inline Utils::ChannelManager &Instance::Get(void)
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
template <> inline Utils::HistoryTracker &Instance::Get(void)
|
||||
{
|
||||
return mHistoryTracker;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (OPENTHREAD_CONFIG_DATASET_UPDATER_ENABLE || OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE) && OPENTHREAD_FTD
|
||||
template <> inline MeshCoP::DatasetUpdater &Instance::Get(void)
|
||||
{
|
||||
|
||||
@@ -177,6 +177,9 @@ void Notifier::EmitEvents(void)
|
||||
#if OPENTHREAD_CONFIG_OTNS_ENABLE
|
||||
Get<Utils::Otns>().HandleNotifierEvents(events);
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<Utils::HistoryTracker>().HandleNotifierEvents(events);
|
||||
#endif
|
||||
#if OPENTHREAD_ENABLE_VENDOR_EXTENSION
|
||||
Get<Extension::ExtensionBase>().HandleNotifierEvents(events);
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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 compile-time configurations for History Tracker module.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CONFIG_HISTORY_TRACKER_H_
|
||||
#define CONFIG_HISTORY_TRACKER_H_
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
*
|
||||
* Define as 1 to enable History Tracker module.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE 0
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_INFO_LIST_SIZE
|
||||
*
|
||||
* Specifies the maximum number of entries in Network Info (role, mode, partition ID, RLOC16) history list.
|
||||
*
|
||||
* Can be set to zero to configure History Tracker module not to collect any entries.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_INFO_LIST_SIZE
|
||||
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_INFO_LIST_SIZE 32
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_RX_LIST_SIZE
|
||||
*
|
||||
* Specifies the maximum number of entries in RX history list.
|
||||
*
|
||||
* Can be set to zero to configure History Tracker module not to collect any RX history.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_RX_LIST_SIZE
|
||||
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_RX_LIST_SIZE 32
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_TX_LIST_SIZE
|
||||
*
|
||||
* Specifies the maximum number of entries in TX history list.
|
||||
*
|
||||
* Can be set to zero to configure History Tracker module not to collect any TX history.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_TX_LIST_SIZE
|
||||
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_TX_LIST_SIZE 32
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_EXCLUDE_THREAD_CONTROL_MESSAGES
|
||||
*
|
||||
* Define as 1 to exclude Thread Control message (e.g., MLE, TMF) from TX and RX history.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_EXCLUDE_THREAD_CONTROL_MESSAGES
|
||||
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_EXCLUDE_THREAD_CONTROL_MESSAGES 1
|
||||
#endif
|
||||
|
||||
#endif // CONFIG_HISTORY_TRACKER_H_
|
||||
@@ -298,9 +298,11 @@ public:
|
||||
const PriorityQueue &GetSendQueue(void) const { return mSendQueue; }
|
||||
|
||||
/**
|
||||
* This static method converts an `IpProto` enumeration to a string.
|
||||
* This static method converts an IP protocol number to a string.
|
||||
*
|
||||
* @returns The string representation of an IP protocol enumeration.
|
||||
* @param[in] aIpPorto An IP protocol number.
|
||||
*
|
||||
* @returns The string representation of @p aIpProto.
|
||||
*
|
||||
*/
|
||||
static const char *IpProtoToString(uint8_t aIpProto);
|
||||
|
||||
@@ -86,15 +86,15 @@ using ot::Encoding::BigEndian::HostSwap32;
|
||||
*/
|
||||
|
||||
// Internet Protocol Numbers
|
||||
static constexpr uint8_t kProtoHopOpts = 0; ///< IPv6 Hop-by-Hop Option
|
||||
static constexpr uint8_t kProtoTcp = 6; ///< Transmission Control Protocol
|
||||
static constexpr uint8_t kProtoUdp = 17; ///< User Datagram
|
||||
static constexpr uint8_t kProtoIp6 = 41; ///< IPv6 encapsulation
|
||||
static constexpr uint8_t kProtoRouting = 43; ///< Routing Header for IPv6
|
||||
static constexpr uint8_t kProtoFragment = 44; ///< Fragment Header for IPv6
|
||||
static constexpr uint8_t kProtoIcmp6 = 58; ///< ICMP for IPv6
|
||||
static constexpr uint8_t kProtoNone = 59; ///< No Next Header for IPv6
|
||||
static constexpr uint8_t kProtoDstOpts = 60; ///< Destination Options for IPv6
|
||||
static constexpr uint8_t kProtoHopOpts = OT_IP6_PROTO_HOP_OPTS; ///< IPv6 Hop-by-Hop Option
|
||||
static constexpr uint8_t kProtoTcp = OT_IP6_PROTO_TCP; ///< Transmission Control Protocol
|
||||
static constexpr uint8_t kProtoUdp = OT_IP6_PROTO_UDP; ///< User Datagram
|
||||
static constexpr uint8_t kProtoIp6 = OT_IP6_PROTO_IP6; ///< IPv6 encapsulation
|
||||
static constexpr uint8_t kProtoRouting = OT_IP6_PROTO_ROUTING; ///< Routing Header for IPv6
|
||||
static constexpr uint8_t kProtoFragment = OT_IP6_PROTO_FRAGMENT; ///< Fragment Header for IPv6
|
||||
static constexpr uint8_t kProtoIcmp6 = OT_IP6_PROTO_ICMP6; ///< ICMP for IPv6
|
||||
static constexpr uint8_t kProtoNone = OT_IP6_PROTO_NONE; ///< No Next Header for IPv6
|
||||
static constexpr uint8_t kProtoDstOpts = OT_IP6_PROTO_DST_OPTS; ///< Destination Options for IPv6
|
||||
|
||||
/**
|
||||
* Class Selectors
|
||||
|
||||
@@ -71,6 +71,7 @@
|
||||
#include "config/dns_client.h"
|
||||
#include "config/dnssd_server.h"
|
||||
#include "config/dtls.h"
|
||||
#include "config/history_tracker.h"
|
||||
#include "config/ip6.h"
|
||||
#include "config/joiner.h"
|
||||
#include "config/link_quality.h"
|
||||
|
||||
@@ -1042,6 +1042,10 @@ void MeshForwarder::UpdateSendMessage(Error aFrameTxError, Mac::Address &aMacDes
|
||||
}
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<Utils::HistoryTracker>().RecordTxMessage(*mSendMessage, aMacDest);
|
||||
#endif
|
||||
|
||||
LogMessage(kMessageTransmit, *mSendMessage, &aMacDest, txError);
|
||||
|
||||
if (mSendMessage->GetType() == Message::kTypeIp6)
|
||||
@@ -1441,6 +1445,10 @@ Error MeshForwarder::HandleDatagram(Message &aMessage, const ThreadLinkInfo &aLi
|
||||
{
|
||||
ThreadNetif &netif = Get<ThreadNetif>();
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<Utils::HistoryTracker>().RecordRxMessage(aMessage, aMacSource);
|
||||
#endif
|
||||
|
||||
LogMessage(kMessageReceive, aMessage, &aMacSource, kErrorNone);
|
||||
|
||||
if (aMessage.GetType() == Message::kTypeIp6)
|
||||
@@ -1620,8 +1628,6 @@ uint16_t MeshForwarder::CalcFrameVersion(const Neighbor *aNeighbor, bool aIePres
|
||||
|
||||
// LCOV_EXCL_START
|
||||
|
||||
#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
|
||||
|
||||
Error MeshForwarder::ParseIp6UdpTcpHeader(const Message &aMessage,
|
||||
Ip6::Header & aIp6Header,
|
||||
uint16_t & aChecksum,
|
||||
@@ -1668,6 +1674,8 @@ exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
|
||||
|
||||
const char *MeshForwarder::MessageActionToString(MessageAction aAction, Error aError)
|
||||
{
|
||||
static const char *const kMessageActionStrings[] = {
|
||||
|
||||
@@ -58,6 +58,10 @@ namespace Mle {
|
||||
class DiscoverScanner;
|
||||
}
|
||||
|
||||
namespace Utils {
|
||||
class HistoryTracker;
|
||||
}
|
||||
|
||||
/**
|
||||
* @addtogroup core-mesh-forwarding
|
||||
*
|
||||
@@ -154,6 +158,7 @@ class MeshForwarder : public InstanceLocator, private NonCopyable
|
||||
friend class IndirectSender;
|
||||
friend class Mle::DiscoverScanner;
|
||||
friend class TimeTicker;
|
||||
friend class Utils::HistoryTracker;
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -503,15 +508,16 @@ private:
|
||||
const Mac::Address &aMacDest,
|
||||
bool aIsSecure);
|
||||
|
||||
static Error ParseIp6UdpTcpHeader(const Message &aMessage,
|
||||
Ip6::Header & aIp6Header,
|
||||
uint16_t & aChecksum,
|
||||
uint16_t & aSourcePort,
|
||||
uint16_t & aDestPort);
|
||||
|
||||
#if (OPENTHREAD_CONFIG_LOG_LEVEL >= OT_LOG_LEVEL_NOTE) && (OPENTHREAD_CONFIG_LOG_MAC == 1)
|
||||
const char *MessageActionToString(MessageAction aAction, Error aError);
|
||||
const char *MessagePriorityToString(const Message &aMessage);
|
||||
|
||||
Error ParseIp6UdpTcpHeader(const Message &aMessage,
|
||||
Ip6::Header & aIp6Header,
|
||||
uint16_t & aChecksum,
|
||||
uint16_t & aSourcePort,
|
||||
uint16_t & aDestPort);
|
||||
#if OPENTHREAD_FTD
|
||||
Error DecompressIp6UdpTcpHeader(const Message & aMessage,
|
||||
uint16_t aOffset,
|
||||
|
||||
+10
-6
@@ -748,6 +748,10 @@ Error Mle::SetDeviceMode(DeviceMode aDeviceMode)
|
||||
VerifyOrExit(mDeviceMode != aDeviceMode);
|
||||
mDeviceMode = aDeviceMode;
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
Get<Utils::HistoryTracker>().RecordNetworkInfo();
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_OTNS_ENABLE
|
||||
Get<Utils::Otns>().EmitDeviceMode(mDeviceMode);
|
||||
#endif
|
||||
@@ -4406,11 +4410,11 @@ const char *Mle::MessageTypeActionToSuffixString(MessageType aType, MessageActio
|
||||
const char *Mle::RoleToString(DeviceRole aRole)
|
||||
{
|
||||
static const char *const kRoleStrings[] = {
|
||||
"Disabled", // (0) kRoleDisabled
|
||||
"Detached", // (1) kRoleDetached
|
||||
"Child", // (2) kRoleChild
|
||||
"Router", // (3) kRoleRouter
|
||||
"Leader", // (4) kRoleLeader
|
||||
"disabled", // (0) kRoleDisabled
|
||||
"detached", // (1) kRoleDetached
|
||||
"child", // (2) kRoleChild
|
||||
"router", // (3) kRoleRouter
|
||||
"leader", // (4) kRoleLeader
|
||||
};
|
||||
|
||||
static_assert(kRoleDisabled == 0, "kRoleDisabled value is incorrect");
|
||||
@@ -4419,7 +4423,7 @@ const char *Mle::RoleToString(DeviceRole aRole)
|
||||
static_assert(kRoleRouter == 3, "kRoleRouter value is incorrect");
|
||||
static_assert(kRoleLeader == 4, "kRoleLeader value is incorrect");
|
||||
|
||||
return kRoleStrings[aRole];
|
||||
return (aRole <= OT_ARRAY_LENGTH(kRoleStrings)) ? kRoleStrings[aRole] : "invalid";
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START
|
||||
|
||||
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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 module.
|
||||
*/
|
||||
|
||||
#include "history_tracker.hpp"
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "common/debug.hpp"
|
||||
#include "common/instance.hpp"
|
||||
#include "common/locator_getters.hpp"
|
||||
#include "common/string.hpp"
|
||||
#include "common/timer.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace Utils {
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
// HistoryTracker
|
||||
|
||||
HistoryTracker::HistoryTracker(Instance &aInstance)
|
||||
: InstanceLocator(aInstance)
|
||||
, mTimer(aInstance, HandleTimer)
|
||||
{
|
||||
mTimer.Start(kAgeCheckPeriod);
|
||||
}
|
||||
|
||||
void HistoryTracker::RecordNetworkInfo(void)
|
||||
{
|
||||
NetworkInfo * entry = mNetInfoHistory.AddNewEntry();
|
||||
Mle::DeviceMode mode;
|
||||
|
||||
VerifyOrExit(entry != nullptr);
|
||||
|
||||
entry->mRole = static_cast<otDeviceRole>(Get<Mle::Mle>().GetRole());
|
||||
entry->mRloc16 = Get<Mle::Mle>().GetRloc16();
|
||||
entry->mPartitionId = Get<Mle::Mle>().GetLeaderData().GetPartitionId();
|
||||
mode = Get<Mle::Mle>().GetDeviceMode();
|
||||
mode.Get(entry->mMode);
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void HistoryTracker::RecordMessage(const Message &aMessage, const Mac::Address &aMacAddresss, MessageType aType)
|
||||
{
|
||||
MessageInfo * entry = nullptr;
|
||||
Ip6::Header ip6Header;
|
||||
Ip6::Icmp::Header icmp6Header;
|
||||
uint8_t ip6Proto;
|
||||
uint16_t checksum;
|
||||
uint16_t sourcePort;
|
||||
uint16_t destPort;
|
||||
|
||||
VerifyOrExit(aMessage.GetType() == Message::kTypeIp6);
|
||||
|
||||
SuccessOrExit(MeshForwarder::ParseIp6UdpTcpHeader(aMessage, ip6Header, checksum, sourcePort, destPort));
|
||||
|
||||
ip6Proto = ip6Header.GetNextHeader();
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_EXCLUDE_THREAD_CONTROL_MESSAGES
|
||||
if (ip6Proto == Ip6::kProtoUdp)
|
||||
{
|
||||
uint16_t port = 0;
|
||||
|
||||
switch (aType)
|
||||
{
|
||||
case kRxMessage:
|
||||
port = destPort;
|
||||
break;
|
||||
|
||||
case kTxMessage:
|
||||
port = sourcePort;
|
||||
break;
|
||||
}
|
||||
|
||||
VerifyOrExit((port != Mle::kUdpPort) && (port != Tmf::kUdpPort));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (ip6Proto == Ip6::kProtoIcmp6)
|
||||
{
|
||||
SuccessOrExit(aMessage.Read(sizeof(Ip6::Header), icmp6Header));
|
||||
checksum = icmp6Header.GetChecksum();
|
||||
}
|
||||
else
|
||||
{
|
||||
icmp6Header.Clear();
|
||||
}
|
||||
|
||||
switch (aType)
|
||||
{
|
||||
case kRxMessage:
|
||||
entry = mRxHistory.AddNewEntry();
|
||||
break;
|
||||
|
||||
case kTxMessage:
|
||||
entry = mTxHistory.AddNewEntry();
|
||||
break;
|
||||
}
|
||||
|
||||
VerifyOrExit(entry != nullptr);
|
||||
|
||||
entry->mPayloadLength = ip6Header.GetPayloadLength();
|
||||
entry->mNeighborRloc16 = aMacAddresss.IsShort() ? aMacAddresss.GetShort() : kInvalidRloc16;
|
||||
entry->mSource.mAddress = ip6Header.GetSource();
|
||||
entry->mSource.mPort = sourcePort;
|
||||
entry->mDestination.mAddress = ip6Header.GetDestination();
|
||||
entry->mDestination.mPort = destPort;
|
||||
entry->mChecksum = checksum;
|
||||
entry->mIpProto = ip6Proto;
|
||||
entry->mIcmp6Type = icmp6Header.GetType();
|
||||
entry->mAveRxRss = (aType == kRxMessage) ? aMessage.GetRssAverager().GetAverage() : kInvalidRss;
|
||||
entry->mLinkSecurity = aMessage.IsLinkSecurityEnabled();
|
||||
entry->mTxSuccess = (aType == kTxMessage) ? aMessage.GetTxSuccess() : true;
|
||||
entry->mPriority = aMessage.GetPriority();
|
||||
|
||||
if (aMacAddresss.IsExtended())
|
||||
{
|
||||
Neighbor *neighbor = Get<NeighborTable>().FindNeighbor(aMacAddresss, Neighbor::kInStateAnyExceptInvalid);
|
||||
|
||||
if (neighbor != nullptr)
|
||||
{
|
||||
entry->mNeighborRloc16 = neighbor->GetRloc16();
|
||||
}
|
||||
}
|
||||
|
||||
#if OPENTHREAD_CONFIG_MULTI_RADIO
|
||||
if (aMessage.IsRadioTypeSet())
|
||||
{
|
||||
switch (aMessage.GetRadioType())
|
||||
{
|
||||
#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
|
||||
case Mac::kRadioTypeIeee802154:
|
||||
entry->mRadioIeee802154 = true;
|
||||
break;
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
|
||||
case Mac::kRadioTypeTrel:
|
||||
entry->mRadioTrelUdp6 = true;
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Radio type may not be set on a tx message indicating that it
|
||||
// was sent over all radio types (e.g., for broadcast frame).
|
||||
// In such a case, we set all supported radios from `else`
|
||||
// block below.
|
||||
}
|
||||
else
|
||||
#endif // OPENTHREAD_CONFIG_MULTI_RADIO
|
||||
{
|
||||
#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
|
||||
entry->mRadioIeee802154 = true;
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
|
||||
entry->mRadioTrelUdp6 = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void HistoryTracker::HandleNotifierEvents(Events aEvents)
|
||||
{
|
||||
if (aEvents.ContainsAny(kEventThreadRoleChanged | kEventThreadRlocAdded | kEventThreadRlocRemoved |
|
||||
kEventThreadPartitionIdChanged))
|
||||
{
|
||||
RecordNetworkInfo();
|
||||
}
|
||||
}
|
||||
|
||||
void HistoryTracker::HandleTimer(Timer &aTimer)
|
||||
{
|
||||
aTimer.Get<HistoryTracker>().HandleTimer();
|
||||
}
|
||||
|
||||
void HistoryTracker::HandleTimer(void)
|
||||
{
|
||||
mNetInfoHistory.UpdateAgedEntries();
|
||||
mRxHistory.UpdateAgedEntries();
|
||||
mTxHistory.UpdateAgedEntries();
|
||||
|
||||
mTimer.Start(kAgeCheckPeriod);
|
||||
}
|
||||
|
||||
void HistoryTracker::EntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize)
|
||||
{
|
||||
StringWriter writer(aBuffer, aSize);
|
||||
|
||||
if (aEntryAge >= kMaxAge)
|
||||
{
|
||||
writer.Append("more than %u days", kMaxAge / kOneDayInMsec);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t days = aEntryAge / kOneDayInMsec;
|
||||
|
||||
if (days > 0)
|
||||
{
|
||||
writer.Append("%u day%s ", days, (days == 1) ? "" : "s");
|
||||
aEntryAge -= days * kOneDayInMsec;
|
||||
}
|
||||
|
||||
writer.Append("%02u:%02u:%02u.%03u", (aEntryAge / kOneHourInMsec),
|
||||
(aEntryAge % kOneDayInMsec) / kOneMinuteInMsec, (aEntryAge % kOneMinuteInMsec) / kOneSecondInMsec,
|
||||
(aEntryAge % kOneSecondInMsec));
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
// HistoryTracker::Timestamp
|
||||
|
||||
void HistoryTracker::Timestamp::SetToNow(void)
|
||||
{
|
||||
mTime = TimerMilli::GetNow();
|
||||
|
||||
// If the current time happens to be the special value which we
|
||||
// use to indicate "distant past", decrement the time by one.
|
||||
|
||||
if (mTime.GetValue() == kDistantPast)
|
||||
{
|
||||
mTime.SetValue(mTime.GetValue() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t HistoryTracker::Timestamp::GetDurationTill(TimeMilli aTime) const
|
||||
{
|
||||
return IsDistantPast() ? kMaxAge : OT_MIN(aTime - mTime, kMaxAge);
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
// HistoryTracker::List
|
||||
|
||||
HistoryTracker::List::List(void)
|
||||
: mStartIndex(0)
|
||||
, mSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
void HistoryTracker::List::Clear(void)
|
||||
{
|
||||
mStartIndex = 0;
|
||||
mSize = 0;
|
||||
}
|
||||
|
||||
uint16_t HistoryTracker::List::Add(uint16_t aMaxSize, Timestamp aTimestamps[])
|
||||
{
|
||||
// Add a new entry and return its list index. Overwrites the
|
||||
// oldest entry if list is full.
|
||||
//
|
||||
// Entries are saved in the order they are added such that
|
||||
// `mStartIndex` is the newest entry and the entries after up
|
||||
// to `mSize` are the previously added entries.
|
||||
|
||||
mStartIndex = (mStartIndex == 0) ? aMaxSize - 1 : mStartIndex - 1;
|
||||
mSize += (mSize == aMaxSize) ? 0 : 1;
|
||||
|
||||
aTimestamps[mStartIndex].SetToNow();
|
||||
|
||||
return mStartIndex;
|
||||
}
|
||||
|
||||
Error HistoryTracker::List::Iterate(uint16_t aMaxSize,
|
||||
const Timestamp aTimestamps[],
|
||||
Iterator & aIterator,
|
||||
uint16_t & aListIndex,
|
||||
uint32_t & aEntryAge) const
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
|
||||
VerifyOrExit(aIterator.GetEntryNumber() < mSize, error = kErrorNotFound);
|
||||
|
||||
aListIndex = MapEntryNumberToListIndex(aIterator.GetEntryNumber(), aMaxSize);
|
||||
aEntryAge = aTimestamps[aListIndex].GetDurationTill(aIterator.GetInitTime());
|
||||
|
||||
aIterator.IncrementEntryNumber();
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
uint16_t HistoryTracker::List::MapEntryNumberToListIndex(uint16_t aEntryNumber, uint16_t aMaxSize) const
|
||||
{
|
||||
// Map the `aEntryNumber` to the list index. `aEntryNumber` value
|
||||
// of zero corresponds to the newest (the most recently added)
|
||||
// entry and value one to next one and so on. List index
|
||||
// warps at the end of array to start of array. Caller MUST
|
||||
// ensure `aEntryNumber` is smaller than `mSize`.
|
||||
|
||||
uint32_t index;
|
||||
|
||||
OT_ASSERT(aEntryNumber < mSize);
|
||||
|
||||
index = static_cast<uint32_t>(aEntryNumber) + mStartIndex;
|
||||
index -= (index >= aMaxSize) ? aMaxSize : 0;
|
||||
|
||||
return static_cast<uint16_t>(index);
|
||||
}
|
||||
|
||||
void HistoryTracker::List::UpdateAgedEntries(uint16_t aMaxSize, Timestamp aTimestamps[])
|
||||
{
|
||||
TimeMilli now = TimerMilli::GetNow();
|
||||
|
||||
// We go through the entries in reverse (starting with the oldest
|
||||
// entry) and check if the entry's age is larger than `kMaxAge`
|
||||
// and if so mark it as "distant past". We can stop as soon as we
|
||||
// get to an entry with age smaller than max.
|
||||
//
|
||||
// The `for()` loop condition is `(entryNumber < mSize)` which
|
||||
// ensures that we go through the loop body for `entryNumber`
|
||||
// value of zero and then in the next iteration (when the
|
||||
// `entryNumber` rolls over) we stop.
|
||||
|
||||
for (uint16_t entryNumber = mSize - 1; entryNumber < mSize; entryNumber--)
|
||||
{
|
||||
uint16_t index = MapEntryNumberToListIndex(entryNumber, aMaxSize);
|
||||
|
||||
if (aTimestamps[index].GetDurationTill(now) < kMaxAge)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
aTimestamps[index].MarkAsDistantPast();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Utils
|
||||
} // namespace ot
|
||||
|
||||
#endif // #if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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 module.
|
||||
*/
|
||||
|
||||
#ifndef HISTORY_TRACKER_HPP_
|
||||
#define HISTORY_TRACKER_HPP_
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#if OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
|
||||
#include <openthread/history_tracker.h>
|
||||
#include <openthread/platform/radio.h>
|
||||
|
||||
#include "common/clearable.hpp"
|
||||
#include "common/locator.hpp"
|
||||
#include "common/non_copyable.hpp"
|
||||
#include "common/notifier.hpp"
|
||||
#include "common/timer.hpp"
|
||||
#include "net/socket.hpp"
|
||||
#include "thread/mesh_forwarder.hpp"
|
||||
#include "thread/mle.hpp"
|
||||
#include "thread/mle_types.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace Utils {
|
||||
|
||||
/**
|
||||
* This class implements History Tracker.
|
||||
*
|
||||
*/
|
||||
class HistoryTracker : public InstanceLocator, private NonCopyable
|
||||
{
|
||||
friend class ot::MeshForwarder;
|
||||
friend class ot::Notifier;
|
||||
friend class ot::Mle::Mle;
|
||||
|
||||
public:
|
||||
/**
|
||||
* This constant specifies the maximum age of entries which is 49 days (value in msec).
|
||||
*
|
||||
* Entries older than the max age will give this value as their age.
|
||||
*
|
||||
*/
|
||||
static constexpr uint32_t kMaxAge = OT_HISTORY_TRACKER_MAX_AGE;
|
||||
|
||||
/**
|
||||
* This constant specifies the recommend string size to represent an entry age
|
||||
*
|
||||
*/
|
||||
static constexpr uint16_t kEntryAgeStringSize = OT_HISTORY_TRACKER_ENTRY_AGE_STRING_SIZE;
|
||||
|
||||
/**
|
||||
* This type represents an iterator to iterate through a history list.
|
||||
*
|
||||
*/
|
||||
class Iterator : public otHistoryTrackerIterator
|
||||
{
|
||||
friend class HistoryTracker;
|
||||
|
||||
public:
|
||||
/**
|
||||
* This method initializes an `Iterator`
|
||||
*
|
||||
* 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(); }
|
||||
|
||||
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(); }
|
||||
};
|
||||
|
||||
/**
|
||||
* This type represents Thread network info.
|
||||
*
|
||||
*/
|
||||
typedef otHistoryTrackerNetworkInfo NetworkInfo;
|
||||
|
||||
/**
|
||||
* This type represents a RX/TX IPv6 message info.
|
||||
*
|
||||
*/
|
||||
typedef otHistoryTrackerMessageInfo MessageInfo;
|
||||
|
||||
/**
|
||||
* This constructor initializes the `HistoryTracker`.
|
||||
*
|
||||
* @param[in] aInstance A reference to the OpenThread instance.
|
||||
*
|
||||
*/
|
||||
explicit HistoryTracker(Instance &aInstance);
|
||||
|
||||
/**
|
||||
* This method iterates over the entries in the network info history list.
|
||||
*
|
||||
* @param[inout] aIterator An iterator. MUST be initialized.
|
||||
* @param[out] aEntryAge A reference to a variable to output the entry's age.
|
||||
* Age is provided as the duration (in milliseconds) from when entry was recorded to
|
||||
* @p aIterator initialization time. It is set to `kMaxAge` for entries older than max age.
|
||||
*
|
||||
* @returns A pointer to `NetworkInfo` entry or `nullptr` if no more entries in the list.
|
||||
*
|
||||
*/
|
||||
const NetworkInfo *IterateNetInfoHistory(Iterator &aIterator, uint32_t &aEntryAge) const
|
||||
{
|
||||
return mNetInfoHistory.Iterate(aIterator, aEntryAge);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method iterates over the entries in the RX history list.
|
||||
*
|
||||
* @param[inout] aIterator An iterator. MUST be initialized.
|
||||
* @param[out] aEntryAge A reference to a variable to output the entry's age.
|
||||
* Age is provided as the duration (in milliseconds) from when entry was recorded to
|
||||
* @p aIterator initialization time. It is set to `kMaxAge` for entries older than max age.
|
||||
*
|
||||
* @returns A pointer to `MessageInfo` entry or `nullptr` if no more entries in the list.
|
||||
*
|
||||
*/
|
||||
const MessageInfo *IterateRxHistory(Iterator &aIterator, uint32_t &aEntryAge) const
|
||||
{
|
||||
return mRxHistory.Iterate(aIterator, aEntryAge);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method iterates over the entries in the TX history list.
|
||||
*
|
||||
* @param[inout] aIterator An iterator. MUST be initialized.
|
||||
* @param[out] aEntryAge A reference to a variable to output the entry's age.
|
||||
* Age is provided as the duration (in milliseconds) from when entry was recorded to
|
||||
* @p aIterator initialization time. It is set to `kMaxAge` for entries older than max age.
|
||||
*
|
||||
* @returns A pointer to `MessageInfo` entry or `nullptr` if no more entries in the list.
|
||||
*
|
||||
*/
|
||||
const MessageInfo *IterateTxHistory(Iterator &aIterator, uint32_t &aEntryAge) const
|
||||
{
|
||||
return mTxHistory.Iterate(aIterator, aEntryAge);
|
||||
}
|
||||
|
||||
/**
|
||||
* This static method converts a given entry age to a human-readable string.
|
||||
*
|
||||
* The entry age string follows the format "<hh>:<mm>:<ss>.<mmmm>" for hours, minutes, seconds and millisecond
|
||||
* (if shorter than one day) or "<dd> days <hh>:<mm>:<ss>.<mmmm>" (if longer than one day).
|
||||
*
|
||||
* If the resulting string does not fit in @p aBuffer (within its @p aSize characters), the string will be
|
||||
* truncated but the outputted string is always null-terminated.
|
||||
*
|
||||
* @param[in] aEntryAge The entry age (duration in msec).
|
||||
* @param[out] aBuffer A pointer to a char array to output the string (MUST NOT be NULL).
|
||||
* @param[in] aSize The size of @p aBuffer (in bytes). Recommended to use `OT_IP6_ADDRESS_STRING_SIZE`.
|
||||
*
|
||||
*/
|
||||
static void EntryAgeToString(uint32_t aEntryAge, char *aBuffer, uint16_t aSize);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kOneSecondInMsec = 1000;
|
||||
static constexpr uint32_t kOneMinuteInMsec = 60 * kOneSecondInMsec;
|
||||
static constexpr uint32_t kOneHourInMsec = 60 * kOneMinuteInMsec;
|
||||
static constexpr uint32_t kOneDayInMsec = 24 * kOneHourInMsec;
|
||||
|
||||
// `Timestamp` uses `uint32_t` value. `2^32` msec is 49 days, 17
|
||||
// hours, 2 minutes and 47 seconds and 296 msec. We use 49 days
|
||||
// as `kMaxAge` and check for aged entries every 16 hours.
|
||||
|
||||
static constexpr uint32_t kAgeCheckPeriod = 16 * kOneHourInMsec;
|
||||
|
||||
static constexpr uint16_t kNetInfoListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_NET_INFO_LIST_SIZE;
|
||||
static constexpr uint16_t kRxListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_RX_LIST_SIZE;
|
||||
static constexpr uint16_t kTxListSize = OPENTHREAD_CONFIG_HISTORY_TRACKER_TX_LIST_SIZE;
|
||||
|
||||
static constexpr int8_t kInvalidRss = OT_RADIO_RSSI_INVALID;
|
||||
static constexpr uint16_t kInvalidRloc16 = Mac::kShortAddrInvalid;
|
||||
|
||||
class Timestamp
|
||||
{
|
||||
public:
|
||||
void SetToNow(void);
|
||||
uint32_t GetDurationTill(TimeMilli aTime) const;
|
||||
bool IsDistantPast(void) const { return (mTime.GetValue() == kDistantPast); }
|
||||
void MarkAsDistantPast(void) { return mTime.SetValue(kDistantPast); }
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kDistantPast = 0;
|
||||
|
||||
TimeMilli mTime;
|
||||
};
|
||||
|
||||
// An ordered list of timestamped items (base class of `EntryList<Entry, kSize>`).
|
||||
class List : private NonCopyable
|
||||
{
|
||||
public:
|
||||
void Clear(void);
|
||||
uint16_t GetSize(void) const { return mSize; }
|
||||
|
||||
protected:
|
||||
List(void);
|
||||
uint16_t Add(uint16_t aMaxSize, Timestamp aTimestamps[]);
|
||||
void UpdateAgedEntries(uint16_t aMaxSize, Timestamp aTimestamps[]);
|
||||
uint16_t MapEntryNumberToListIndex(uint16_t aEntryNumber, uint16_t aMaxSize) const;
|
||||
Error Iterate(uint16_t aMaxSize,
|
||||
const Timestamp aTimestamps[],
|
||||
Iterator & aIterator,
|
||||
uint16_t & aListIndex,
|
||||
uint32_t & aEntryAge) const;
|
||||
|
||||
private:
|
||||
uint16_t mStartIndex;
|
||||
uint16_t mSize;
|
||||
};
|
||||
|
||||
// A history list (with given max size) of timestamped `Entry` items.
|
||||
template <typename Entry, uint16_t kMaxSize> class EntryList : public List
|
||||
{
|
||||
public:
|
||||
// Adds a new entry to the list or overwrites the oldest entry
|
||||
// if list is full. First version returns a pointer to the
|
||||
// new `Entry` (for caller to populate). Second version copies
|
||||
// the given `aEntry`.
|
||||
Entry *AddNewEntry(void) { return &mEntries[Add(kMaxSize, mTimestamps)]; }
|
||||
void AddNewEntry(const Entry &aEntry) { mEntries[Add(kMaxSize, mTimestamps)] = aEntry; }
|
||||
|
||||
void UpdateAgedEntries(void) { List::UpdateAgedEntries(kMaxSize, mTimestamps); }
|
||||
|
||||
const Entry *Iterate(Iterator &aIterator, uint32_t &aEntryAge) const
|
||||
{
|
||||
uint16_t index;
|
||||
|
||||
return (List::Iterate(kMaxSize, mTimestamps, aIterator, index, aEntryAge) == kErrorNone) ? &mEntries[index]
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
Timestamp mTimestamps[kMaxSize];
|
||||
Entry mEntries[kMaxSize];
|
||||
};
|
||||
|
||||
// Partial specialization for `kMaxSize` zero.
|
||||
template <typename Entry> class EntryList<Entry, 0> : private NonCopyable
|
||||
{
|
||||
public:
|
||||
void Clear(void) {}
|
||||
uint16_t GetSize(void) const { return 0; }
|
||||
Entry * AddNewEntry(void) { return nullptr; }
|
||||
void AddNewEntry(const Entry &) {}
|
||||
const Entry *Iterate(Iterator &, uint32_t &) const { return nullptr; }
|
||||
void RemoveAgedEntries(void) {}
|
||||
};
|
||||
|
||||
enum MessageType : uint8_t
|
||||
{
|
||||
kRxMessage,
|
||||
kTxMessage,
|
||||
};
|
||||
|
||||
void RecordRxMessage(const Message &aMessage, const Mac::Address &aMacSource)
|
||||
{
|
||||
RecordMessage(aMessage, aMacSource, kRxMessage);
|
||||
}
|
||||
|
||||
void RecordTxMessage(const Message &aMessage, const Mac::Address &aMacDest)
|
||||
{
|
||||
RecordMessage(aMessage, aMacDest, kTxMessage);
|
||||
}
|
||||
|
||||
void RecordNetworkInfo(void);
|
||||
void RecordMessage(const Message &aMessage, const Mac::Address &aMacAddress, MessageType aType);
|
||||
void HandleNotifierEvents(Events aEvents);
|
||||
static void HandleTimer(Timer &aTimer);
|
||||
void HandleTimer(void);
|
||||
|
||||
EntryList<NetworkInfo, kNetInfoListSize> mNetInfoHistory;
|
||||
EntryList<MessageInfo, kRxListSize> mRxHistory;
|
||||
EntryList<MessageInfo, kTxListSize> mTxHistory;
|
||||
TimerMilli mTimer;
|
||||
};
|
||||
|
||||
} // namespace Utils
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
|
||||
#endif // HISTORY_TRACKER_HPP_
|
||||
@@ -53,6 +53,7 @@ DNS_CLIENT ?= 1
|
||||
DNSSD_SERVER ?= 1
|
||||
DYNAMIC_LOG_LEVEL ?= 1
|
||||
ECDSA ?= 1
|
||||
HISTORY_TRACKER ?= 1
|
||||
IP6_FRAGM ?= 1
|
||||
JAM_DETECTION ?= 1
|
||||
JOINER ?= 1
|
||||
|
||||
@@ -217,6 +217,16 @@
|
||||
#define OPENTHREAD_CONFIG_IP6_MAX_EXT_MCAST_ADDRS 8
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
*
|
||||
* Define as 1 to enable History Tracker module.
|
||||
*
|
||||
*/
|
||||
#ifndef OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE 1
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_HEAP_INTERNAL_SIZE
|
||||
*
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
-DOT_DHCP6_SERVER=ON \
|
||||
-DOT_DNS_CLIENT=ON \
|
||||
-DOT_ECDSA=ON \
|
||||
-DOT_HISTORY_TRACKER=ON \
|
||||
-DOT_IP6_FRAGM=ON \
|
||||
-DOT_JAM_DETECTION=ON \
|
||||
-DOT_JOINER=ON \
|
||||
|
||||
@@ -162,6 +162,7 @@ EXTRA_DIST = \
|
||||
test_diag.py \
|
||||
test_dns_client_config_auto_start.py \
|
||||
test_dnssd.py \
|
||||
test_history_tracker.py \
|
||||
test_ipv6.py \
|
||||
test_ipv6_fragmentation.py \
|
||||
test_ipv6_source_selection.py \
|
||||
@@ -228,6 +229,7 @@ check_SCRIPTS = \
|
||||
test_diag.py \
|
||||
test_dns_client_config_auto_start.py \
|
||||
test_dnssd.py \
|
||||
test_history_tracker.py \
|
||||
test_ipv6.py \
|
||||
test_ipv6_fragmentation.py \
|
||||
test_ipv6_source_selection.py \
|
||||
|
||||
@@ -1331,6 +1331,10 @@ class NodeImpl:
|
||||
self.send_command(cmd)
|
||||
self._expect_done()
|
||||
|
||||
def get_partition_id(self):
|
||||
self.send_command('partitionid')
|
||||
return self._expect_result(r'\d+')
|
||||
|
||||
def get_preferred_partition_id(self):
|
||||
self.send_command('partitionid preferred')
|
||||
return self._expect_result(r'\d+')
|
||||
@@ -2720,6 +2724,112 @@ class NodeImpl:
|
||||
self.send_command(cmd)
|
||||
self._expect_command_output(cmd)
|
||||
|
||||
def history_netinfo(self, num_entries=0):
|
||||
"""
|
||||
Get the `netinfo` history list, parse each entry and return
|
||||
a list of dictionary (string key and string value) entries.
|
||||
|
||||
Example of return value:
|
||||
[
|
||||
{
|
||||
'age': '00:00:00.000 ago',
|
||||
'role': 'disabled',
|
||||
'mode': 'rdn',
|
||||
'rloc16': '0x7400',
|
||||
'partition-id': '1318093703'
|
||||
},
|
||||
{
|
||||
'age': '00:00:02.588 ago',
|
||||
'role': 'leader',
|
||||
'mode': 'rdn',
|
||||
'rloc16': '0x7400',
|
||||
'partition-id': '1318093703'
|
||||
}
|
||||
]
|
||||
"""
|
||||
cmd = f'history netinfo list {num_entries}'
|
||||
self.send_command(cmd)
|
||||
output = self._expect_command_output(cmd)
|
||||
netinfos = []
|
||||
for entry in output:
|
||||
netinfo = {}
|
||||
age, info = entry.split(' -> ')
|
||||
netinfo['age'] = age
|
||||
for item in info.split(' '):
|
||||
k, v = item.split(':')
|
||||
netinfo[k] = v
|
||||
netinfos.append(netinfo)
|
||||
return netinfos
|
||||
|
||||
def history_rx(self, num_entries=0):
|
||||
"""
|
||||
Get the IPv6 RX history list, parse each entry and return
|
||||
a list of dictionary (string key and string value) entries.
|
||||
|
||||
Example of return value:
|
||||
[
|
||||
{
|
||||
'age': '00:00:01.999',
|
||||
'type': 'ICMP6(EchoReqst)',
|
||||
'len': '16',
|
||||
'sec': 'yes',
|
||||
'prio': 'norm',
|
||||
'rss': '-20',
|
||||
'from': '0xac00',
|
||||
'radio': '15.4',
|
||||
'src': '[fd00:db8:0:0:2cfa:fd61:58a9:f0aa]:0',
|
||||
'dst': '[fd00:db8:0:0:ed7e:2d04:e543:eba5]:0',
|
||||
}
|
||||
]
|
||||
"""
|
||||
cmd = f'history rx list {num_entries}'
|
||||
self.send_command(cmd)
|
||||
return self._parse_history_rx_tx_ouput(self._expect_command_output(cmd))
|
||||
|
||||
def history_tx(self, num_entries=0):
|
||||
"""
|
||||
Get the IPv6 TX history list, parse each entry and return
|
||||
a list of dictionary (string key and string value) entries.
|
||||
|
||||
Example of return value:
|
||||
[
|
||||
{
|
||||
'age': '00:00:01.999',
|
||||
'type': 'ICMP6(EchoReply)',
|
||||
'len': '16',
|
||||
'sec': 'yes',
|
||||
'prio': 'norm',
|
||||
'to': '0xac00',
|
||||
'tx-success': 'yes',
|
||||
'radio': '15.4',
|
||||
'src': '[fd00:db8:0:0:ed7e:2d04:e543:eba5]:0',
|
||||
'dst': '[fd00:db8:0:0:2cfa:fd61:58a9:f0aa]:0',
|
||||
|
||||
}
|
||||
]
|
||||
"""
|
||||
cmd = f'history tx list {num_entries}'
|
||||
self.send_command(cmd)
|
||||
return self._parse_history_rx_tx_ouput(self._expect_command_output(cmd))
|
||||
|
||||
def _parse_history_rx_tx_ouput(self, lines):
|
||||
rxtx_list = []
|
||||
for line in lines:
|
||||
if line.strip().startswith('type:'):
|
||||
for item in line.strip().split(' '):
|
||||
k, v = item.split(':')
|
||||
entry[k] = v
|
||||
elif line.strip().startswith('src:'):
|
||||
entry['src'] = line[4:]
|
||||
elif line.strip().startswith('dst:'):
|
||||
entry['dst'] = line[4:]
|
||||
rxtx_list.append(entry)
|
||||
else:
|
||||
entry = {}
|
||||
entry['age'] = line
|
||||
|
||||
return rxtx_list
|
||||
|
||||
|
||||
class Node(NodeImpl, OtCli):
|
||||
pass
|
||||
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright (c) 2021, 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.
|
||||
#
|
||||
|
||||
import os
|
||||
import unittest
|
||||
import sys
|
||||
import thread_cert
|
||||
|
||||
# Test description:
|
||||
# This test verifies History Tracker behavior.
|
||||
#
|
||||
# Topology:
|
||||
#
|
||||
# LEADER
|
||||
# |
|
||||
# |
|
||||
# CHILD
|
||||
#
|
||||
|
||||
LEADER = 1
|
||||
CHILD = 2
|
||||
|
||||
SHORT_WAIT = 5
|
||||
ONE_DAY = 24 * 60 * 60
|
||||
MAX_AGE_IN_DAYS = 49
|
||||
|
||||
|
||||
class TestHistoryTracker(thread_cert.TestCase):
|
||||
USE_MESSAGE_FACTORY = False
|
||||
SUPPORT_NCP = False
|
||||
|
||||
TOPOLOGY = {
|
||||
LEADER: {
|
||||
'name': 'Leader',
|
||||
'mode': 'rdn',
|
||||
},
|
||||
CHILD: {
|
||||
'name': 'Child',
|
||||
'mode': 'n',
|
||||
},
|
||||
}
|
||||
|
||||
def test(self):
|
||||
leader = self.nodes[LEADER]
|
||||
child = self.nodes[CHILD]
|
||||
|
||||
# Start the leader and verify that 'netinfo' history
|
||||
# is updated correctly.
|
||||
|
||||
leader.start()
|
||||
self.simulator.go(SHORT_WAIT)
|
||||
self.assertEqual(leader.get_state(), 'leader')
|
||||
|
||||
netinfo = leader.history_netinfo()
|
||||
self.assertEqual(len(netinfo), 2)
|
||||
self.assertEqual(netinfo[0]['role'], 'leader')
|
||||
self.assertEqual(netinfo[0]['mode'], 'rdn')
|
||||
self.assertEqual(int(netinfo[0]['rloc16'], 16), leader.get_addr16())
|
||||
self.assertEqual(netinfo[0]['partition-id'], leader.get_partition_id())
|
||||
self.assertEqual(netinfo[1]['role'], 'detached')
|
||||
|
||||
# Stop the leader
|
||||
|
||||
leader.thread_stop()
|
||||
leader.interface_down()
|
||||
self.simulator.go(SHORT_WAIT)
|
||||
netinfo = leader.history_netinfo(2)
|
||||
self.assertEqual(len(netinfo), 2)
|
||||
self.assertEqual(netinfo[0]['role'], 'disabled')
|
||||
self.assertEqual(netinfo[1]['role'], 'leader')
|
||||
|
||||
# Wait for one day, two days, then up to max age and verify that
|
||||
# `netinfo` entry age is updated correctly.
|
||||
#
|
||||
# Since we want to wait for long duration (49 days), to speed up
|
||||
# the simulation time, we disable leader to avoid the need to
|
||||
# to simulate all the message/events (e.g. MLE adv) while thread
|
||||
# is operational.
|
||||
|
||||
self.simulator.go(ONE_DAY)
|
||||
netinfo = leader.history_netinfo(1)
|
||||
self.assertTrue(netinfo[0]['age'].startswith('1 day'))
|
||||
|
||||
self.simulator.go(ONE_DAY)
|
||||
netinfo = leader.history_netinfo(1)
|
||||
self.assertTrue(netinfo[0]['age'].startswith('2 days'))
|
||||
|
||||
self.simulator.go((MAX_AGE_IN_DAYS - 3) * ONE_DAY)
|
||||
netinfo = leader.history_netinfo(1)
|
||||
self.assertTrue(netinfo[0]['age'].startswith('{} days'.format(MAX_AGE_IN_DAYS - 1)))
|
||||
|
||||
self.simulator.go(ONE_DAY)
|
||||
netinfo = leader.history_netinfo(1)
|
||||
self.assertTrue(netinfo[0]['age'].startswith('more than {} days'.format(MAX_AGE_IN_DAYS)))
|
||||
|
||||
self.simulator.go(2 * ONE_DAY)
|
||||
netinfo = leader.history_netinfo(1)
|
||||
self.assertTrue(netinfo[0]['age'].startswith('more than {} days'.format(MAX_AGE_IN_DAYS)))
|
||||
|
||||
# Start leader and child
|
||||
|
||||
leader.start()
|
||||
self.simulator.go(SHORT_WAIT)
|
||||
self.assertEqual(leader.get_state(), 'leader')
|
||||
|
||||
child.start()
|
||||
self.simulator.go(SHORT_WAIT)
|
||||
self.assertEqual(child.get_state(), 'child')
|
||||
|
||||
child_rloc16 = child.get_addr16()
|
||||
leader_rloc16 = leader.get_addr16()
|
||||
|
||||
# Verify the `netinfo` history on child
|
||||
|
||||
netinfo = child.history_netinfo(2)
|
||||
self.assertEqual(len(netinfo), 2)
|
||||
self.assertEqual(netinfo[0]['role'], 'child')
|
||||
self.assertEqual(netinfo[0]['mode'], 'n')
|
||||
self.assertEqual(int(netinfo[0]['rloc16'], 16), child_rloc16)
|
||||
self.assertEqual(netinfo[0]['partition-id'], leader.get_partition_id())
|
||||
self.assertEqual(netinfo[1]['role'], 'detached')
|
||||
|
||||
# Change the child mode and verify that `netinfo` history
|
||||
# records this change.
|
||||
|
||||
child.set_mode('rn')
|
||||
self.simulator.go(SHORT_WAIT)
|
||||
netinfo = child.history_netinfo(1)
|
||||
self.assertEqual(len(netinfo), 1)
|
||||
self.assertEqual(netinfo[0]['mode'], 'rn')
|
||||
|
||||
# Ping from leader to child and check the RX and TX history
|
||||
# on child and leader.
|
||||
|
||||
child_mleid = child.get_mleid()
|
||||
leader_mleid = leader.get_mleid()
|
||||
|
||||
ping_sizes = [10, 100, 1000]
|
||||
num_msgs = len(ping_sizes)
|
||||
|
||||
for size in ping_sizes:
|
||||
leader.ping(child_mleid, size=size)
|
||||
|
||||
leader_tx = leader.history_tx(num_msgs)
|
||||
leader_rx = leader.history_rx(num_msgs)
|
||||
child_tx = child.history_tx(num_msgs)
|
||||
child_rx = child.history_rx(num_msgs)
|
||||
|
||||
for index in range(num_msgs):
|
||||
self.assertEqual(leader_tx[index]['type'], 'ICMP6(EchoReqst)')
|
||||
self.assertEqual(leader_tx[index]['sec'], 'yes')
|
||||
self.assertEqual(leader_tx[index]['prio'], 'norm')
|
||||
self.assertEqual(leader_tx[index]['tx-success'], 'yes')
|
||||
self.assertEqual(leader_tx[index]['radio'], '15.4')
|
||||
self.assertEqual(int(leader_tx[index]['to'], 16), child_rloc16)
|
||||
self.assertEqual(leader_tx[index]['src'][1:-3], leader_mleid)
|
||||
self.assertEqual(leader_tx[index]['dst'][1:-3], child_mleid)
|
||||
|
||||
self.assertEqual(child_rx[index]['type'], 'ICMP6(EchoReqst)')
|
||||
self.assertEqual(child_rx[index]['sec'], 'yes')
|
||||
self.assertEqual(child_rx[index]['prio'], 'norm')
|
||||
self.assertEqual(child_rx[index]['radio'], '15.4')
|
||||
self.assertEqual(int(child_rx[index]['from'], 16), leader_rloc16)
|
||||
self.assertEqual(child_rx[index]['src'][1:-3], leader_mleid)
|
||||
self.assertEqual(child_rx[index]['dst'][1:-3], child_mleid)
|
||||
|
||||
self.assertEqual(leader_rx[index]['type'], 'ICMP6(EchoReply)')
|
||||
self.assertEqual(child_tx[index]['type'], 'ICMP6(EchoReply)')
|
||||
|
||||
self.assertEqual(leader_tx[index]['len'], child_rx[index]['len'])
|
||||
self.assertEqual(leader_rx[index]['len'], child_tx[index]['len'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# FIXME: We skip the test under distcheck build (the simulation
|
||||
# under this build for some reason cannot seem to handle longer
|
||||
# wait times - days up to 50 days in this test). We return error
|
||||
# code 77 which indicates that this test case was skipped (in
|
||||
# automake).
|
||||
|
||||
if os.getenv('DISTCHECK_BUILD') == '1':
|
||||
sys.exit(77)
|
||||
|
||||
unittest.main()
|
||||
@@ -486,6 +486,14 @@
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_SRP_CLIENT_DOMAIN_NAME_API_ENABLE 1
|
||||
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE
|
||||
*
|
||||
* Define as 1 to enable History Tracker module.
|
||||
*
|
||||
*/
|
||||
#define OPENTHREAD_CONFIG_HISTORY_TRACKER_ENABLE 1
|
||||
|
||||
#if OPENTHREAD_RADIO
|
||||
/**
|
||||
* @def OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE
|
||||
|
||||
Reference in New Issue
Block a user