diff --git a/include/openthread/instance.h b/include/openthread/instance.h index ede27056c..3920f6484 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (340) +#define OPENTHREAD_API_VERSION (341) /** * @addtogroup api-instance diff --git a/include/openthread/mesh_diag.h b/include/openthread/mesh_diag.h index cd9cb3509..e21e47c93 100644 --- a/include/openthread/mesh_diag.h +++ b/include/openthread/mesh_diag.h @@ -82,7 +82,7 @@ typedef struct otMeshDiagIp6AddrIterator otMeshDiagIp6AddrIterator; typedef struct otMeshDiagChildIterator otMeshDiagChildIterator; /** - * This constant indicates that Thread Version is unknown. + * Specifies that Thread Version is unknown. * * This is used in `otMeshDiagRouterInfo` for `mVersion` property when device does not provide its version. This * indicates that device is likely running 1.3.0 (version value 4) or earlier. @@ -91,7 +91,7 @@ typedef struct otMeshDiagChildIterator otMeshDiagChildIterator; #define OT_MESH_DIAG_VERSION_UNKNOWN 0xffff /** - * Represents information about a router in Thread mesh. + * Represents information about a router in Thread mesh discovered using `otMeshDiagDiscoverTopology()`. * */ typedef struct otMeshDiagRouterInfo @@ -106,8 +106,8 @@ typedef struct otMeshDiagRouterInfo bool mIsBorderRouter : 1; ///< Whether router acts as a border router providing ext connectivity. /** - * This array provides the link quality from this router to other routers, also indicating whether a link is - * established between the routers. + * Provides the link quality from this router to other routers, also indicating whether a link is established + * between the routers. * * The array is indexed based on Router ID. `mLinkQualities[routerId]` indicates the incoming link quality, the * router sees to the router with `routerId`. Link quality is a value in [0, 3]. Value zero indicates no link. @@ -142,7 +142,7 @@ typedef struct otMeshDiagRouterInfo } otMeshDiagRouterInfo; /** - * Represents information about a discovered child in Thread mesh. + * Represents information about a discovered child in Thread mesh using `otMeshDiagDiscoverTopology()`. * */ typedef struct otMeshDiagChildInfo @@ -198,7 +198,12 @@ otError otMeshDiagDiscoverTopology(otInstance *aInstance, void otMeshDiagCancel(otInstance *aInstance); /** - * Iterates through the discovered IPv6 address of a router. + * Iterates through the discovered IPv6 addresses of a router or an MTD child. + * + * MUST be used + * - from the callback `otMeshDiagDiscoverCallback()` and use the `mIp6AddrIterator` from the `aRouterInfo` struct that + * is provided as input to the callback, or + * - from the callback `otMeshDiagChildIp6AddrsCallback()` along with provided `aIp6AddrIterator`. * * @param[in,out] aIterator The address iterator to use. * @param[out] aIp6Address A pointer to return the next IPv6 address (if any). @@ -212,6 +217,9 @@ otError otMeshDiagGetNextIp6Address(otMeshDiagIp6AddrIterator *aIterator, otIp6A /** * Iterates through the discovered children of a router. * + * This function MUST be used from the callback `otMeshDiagDiscoverCallback()` and use the `mChildIterator` from the + * `aRouterInfo` struct that is provided as input to the callback. + * * @param[in,out] aIterator The address iterator to use. * @param[out] aChildInfo A pointer to return the child info (if any). * @@ -221,6 +229,181 @@ otError otMeshDiagGetNextIp6Address(otMeshDiagIp6AddrIterator *aIterator, otIp6A */ otError otMeshDiagGetNextChildInfo(otMeshDiagChildIterator *aIterator, otMeshDiagChildInfo *aChildInfo); +/** + * Represents information about a child entry from `otMeshDiagQueryChildTable()`. + * + * `mSupportsErrRate` indicates whether or not the error tracking feature is supported and `mFrameErrorRate` and + * `mMessageErrorRate` values are valid. The frame error rate tracks frame tx errors (towards the child) at MAC + * layer, while `mMessageErrorRate` tracks the IPv6 message error rate (above MAC layer and after MAC retries) when + * an IPv6 message is dropped. For example, if the message is large and requires 6LoWPAN fragmentation, message tx is + * considered as failed if one of its fragment frame tx fails (for example, never acked). + * + */ +typedef struct otMeshDiagChildEntry +{ + bool mRxOnWhenIdle : 1; ///< Is rx-on when idle (vs sleepy). + bool mDeviceTypeFtd : 1; ///< Is device FTD (vs MTD). + bool mFullNetData : 1; ///< Whether device gets full Network Data (vs stable sub-set). + bool mCslSynchronized : 1; ///< Is CSL capable and CSL synchronized. + bool mSupportsErrRate : 1; ///< `mFrameErrorRate` and `mMessageErrorRate` values are valid. + uint16_t mRloc16; ///< RLOC16. + otExtAddress mExtAddress; ///< Extended Address. + uint16_t mVersion; ///< Version. + uint32_t mTimeout; ///< Timeout in seconds. + uint32_t mAge; ///< Seconds since last heard from the child. + uint32_t mConnectionTime; ///< Seconds since child attach. + uint16_t mSupervisionInterval; ///< Supervision interval in seconds. Zero to indicate not used. + uint8_t mLinkMargin; ///< Link Margin in dB. + int8_t mAverageRssi; ///< Average RSSI. + int8_t mLastRssi; ///< RSSI of last received frame. + uint16_t mFrameErrorRate; ///< Frame error rate (0x0000->0%, 0xffff->100%). + uint16_t mMessageErrorRate; ///< (IPv6) msg error rate (0x0000->0%, 0xffff->100%). + uint16_t mQueuedMessageCount; ///< Number of queued messages for indirect tx to child. + uint16_t mCslPeriod; ///< CSL Period in unit of 10-symbols-time. Zero indicates CSL is disabled. + uint32_t mCslTimeout; ///< CSL Timeout in seconds. + uint8_t mCslChannel; ///< CSL channel. +} otMeshDiagChildEntry; + +/** + * Represents the callback used by `otMeshDiagQueryChildTable()` to provide information about child table entries. + * + * When @p aError is `OT_ERROR_PENDING`, it indicates that the table still has more entries and the callback will be + * invoked again. + * + * @param[in] aError OT_ERROR_PENDING Indicates there are more entries in the table. + * OT_ERROR_NONE Indicates the table is finished. + * OT_ERROR_RESPONSE_TIMEOUT Timed out waiting for response. + * @param[in] aChildEntry The child entry (can be null if `aError` is OT_ERROR_RESPONSE_TIMEOUT or OT_ERROR_NONE). + * @param[in] aContext Application-specific context. + * + */ +typedef void (*otMeshDiagQueryChildTableCallback)(otError aError, + const otMeshDiagChildEntry *aChildEntry, + void *aContext); + +/** + * Starts query for child table for a given router. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aRloc16 The RLOC16 of router to query. + * @param[in] aCallback The callback to report the queried child table. + * @param[in] aContext A context to pass in @p aCallback. + * + * @retval OT_ERROR_NONE The query started successfully. + * @retval OT_ERROR_BUSY A previous discovery or query request is still ongoing. + * @retval OT_ERROR_INVALID_ARGS The @p aRloc16 is not a valid router RLOC16. + * @retval OT_ERROR_INVALID_STATE Device is not attached. + * @retval OT_ERROR_NO_BUFS Could not allocate buffer to send query messages. + * + */ +otError otMeshDiagQueryChildTable(otInstance *aInstance, + uint16_t aRloc16, + otMeshDiagQueryChildTableCallback aCallback, + void *aContext); + +/** + * Represents the callback used by `otMeshDiagQueryChildrenIp6Addrs()` to provide information about an MTD child and + * its list of IPv6 addresses. + * + * When @p aError is `OT_ERROR_PENDING`, it indicates that there are more children and the callback will be invoked + * again. + * + * @param[in] aError OT_ERROR_PENDING Indicates there are more children in the table. + * OT_ERROR_NONE Indicates the table is finished. + * OT_ERROR_RESPONSE_TIMEOUT Timed out waiting for response. + * @param[in] aChildRloc16 The RLOC16 of the child. `0xfffe` is used on `OT_ERROR_RESPONSE_TIMEOUT`. + * @param[in] aIp6AddrIterator An iterator to go through the IPv6 addresses of the child with @p aRloc using + * `otMeshDiagGetNextIp6Address()`. Set to NULL on `OT_ERROR_RESPONSE_TIMEOUT`. + * @param[in] aContext Application-specific context. + * + */ +typedef void (*otMeshDiagChildIp6AddrsCallback)(otError aError, + uint16_t aChildRloc16, + otMeshDiagIp6AddrIterator *aIp6AddrIterator, + void *aContext); + +/** + * Sends a query to a parent to retrieve the IPv6 addresses of all its MTD children. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aRloc16 The RLOC16 of parent to query. + * @param[in] aCallback The callback to report the queried child IPv6 address list. + * @param[in] aContext A context to pass in @p aCallback. + * + * @retval OT_ERROR_NONE The query started successfully. + * @retval OT_ERROR_BUSY A previous discovery or query request is still ongoing. + * @retval OT_ERROR_INVALID_ARGS The @p aRloc16 is not a valid RLOC16. + * @retval OT_ERROR_INVALID_STATE Device is not attached. + * @retval OT_ERROR_NO_BUFS Could not allocate buffer to send query messages. + * + */ +otError otMeshDiagQueryChildrenIp6Addrs(otInstance *aInstance, + uint16_t aRloc16, + otMeshDiagChildIp6AddrsCallback aCallback, + void *aContext); + +/** + * Represents information about a router neighbor entry from `otMeshDiagQueryRouterNeighborTable()`. + * + * `mSupportsErrRate` indicates whether or not the error tracking feature is supported and `mFrameErrorRate` and + * `mMessageErrorRate` values are valid. The frame error rate tracks frame tx errors (towards the child) at MAC + * layer, while `mMessageErrorRate` tracks the IPv6 message error rate (above MAC layer and after MAC retries) when + * an IPv6 message is dropped. For example, if the message is large and requires 6LoWPAN fragmentation, message tx is + * considered as failed if one of its fragment frame tx fails (for example, never acked). + * + */ +typedef struct otMeshDiagRouterNeighborEntry +{ + bool mSupportsErrRate : 1; ///< `mFrameErrorRate` and `mMessageErrorRate` values are valid. + uint16_t mRloc16; ///< RLOC16. + otExtAddress mExtAddress; ///< Extended Address. + uint16_t mVersion; ///< Version. + uint32_t mConnectionTime; ///< Seconds since link establishment. + uint8_t mLinkMargin; ///< Link Margin in dB. + int8_t mAverageRssi; ///< Average RSSI. + int8_t mLastRssi; ///< RSSI of last received frame. + uint16_t mFrameErrorRate; ///< Frame error rate (0x0000->0%, 0xffff->100%). + uint16_t mMessageErrorRate; ///< (IPv6) msg error rate (0x0000->0%, 0xffff->100%). +} otMeshDiagRouterNeighborEntry; + +/** + * Represents the callback used by `otMeshDiagQueryRouterNeighborTable()` to provide information about neighbor router + * table entries. + * + * When @p aError is `OT_ERROR_PENDING`, it indicates that the table still has more entries and the callback will be + * invoked again. + * + * @param[in] aError OT_ERROR_PENDING Indicates there are more entries in the table. + * OT_ERROR_NONE Indicates the table is finished. + * OT_ERROR_RESPONSE_TIMEOUT Timed out waiting for response. + * @param[in] aNeighborEntry The neighbor entry (can be null if `aError` is RESPONSE_TIMEOUT or NONE). + * @param[in] aContext Application-specific context. + * + */ +typedef void (*otMeshDiagQueryRouterNeighborTableCallback)(otError aError, + const otMeshDiagRouterNeighborEntry *aNeighborEntry, + void *aContext); + +/** + * Starts query for router neighbor table for a given router. + * + * @param[in] aInstance The OpenThread instance. + * @param[in] aRloc16 The RLOC16 of router to query. + * @param[in] aCallback The callback to report the queried table. + * @param[in] aContext A context to pass in @p aCallback. + * + * @retval OT_ERROR_NONE The query started successfully. + * @retval OT_ERROR_BUSY A previous discovery or query request is still ongoing. + * @retval OT_ERROR_INVALID_ARGS The @p aRloc16 is not a valid router RLOC16. + * @retval OT_ERROR_INVALID_STATE Device is not attached. + * @retval OT_ERROR_NO_BUFS Could not allocate buffer to send query messages. + * + */ +otError otMeshDiagQueryRouterNeighborTable(otInstance *aInstance, + uint16_t aRloc16, + otMeshDiagQueryRouterNeighborTableCallback aCallback, + void *aContext); + /** * @} * diff --git a/include/openthread/netdiag.h b/include/openthread/netdiag.h index 03563eaa5..040ad75d1 100644 --- a/include/openthread/netdiag.h +++ b/include/openthread/netdiag.h @@ -87,6 +87,13 @@ enum OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_MODEL = 26, ///< Vendor Model TLV OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_SW_VERSION = 27, ///< Vendor SW Version TLV OT_NETWORK_DIAGNOSTIC_TLV_THREAD_STACK_VERSION = 28, ///< Thread Stack Version TLV + OT_NETWORK_DIAGNOSTIC_TLV_CHILD = 29, ///< Child TLV + OT_NETWORK_DIAGNOSTIC_TLV_CHILD_IP6_ADDR_LIST = 30, ///< Child IPv6 Address List TLV + OT_NETWORK_DIAGNOSTIC_TLV_ROUTER_NEIGHBOR = 31, ///< Router Neighbor TLV + OT_NETWORK_DIAGNOSTIC_TLV_ANSWER = 32, ///< Answer TLV + OT_NETWORK_DIAGNOSTIC_TLV_QUERY_ID = 33, ///< Query ID TLV + OT_NETWORK_DIAGNOSTIC_TLV_MLE_COUNTERS = 34, ///< MLE Counters TLV + }; #define OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_NAME_TLV_LENGTH 32 ///< Max length of Vendor Name TLV. @@ -201,6 +208,29 @@ typedef struct otNetworkDiagMacCounters uint32_t mIfOutDiscards; } otNetworkDiagMacCounters; +/** + * Represents a Network Diagnostics MLE Counters value. + * + */ +typedef struct otNetworkDiagMleCounters +{ + uint16_t mDisabledRole; ///< Number of times device entered disabled role. + uint16_t mDetachedRole; ///< Number of times device entered detached role. + uint16_t mChildRole; ///< Number of times device entered child role. + uint16_t mRouterRole; ///< Number of times device entered router role. + uint16_t mLeaderRole; ///< Number of times device entered leader role. + uint16_t mAttachAttempts; ///< Number of attach attempts while device was detached. + uint16_t mPartitionIdChanges; ///< Number of changes to partition ID. + uint16_t mBetterPartitionAttachAttempts; ///< Number of attempts to attach to a better partition. + uint16_t mParentChanges; ///< Number of time device changed its parent. + uint64_t mTrackedTime; ///< Milliseconds tracked by next counters (zero if not supported). + uint64_t mDisabledTime; ///< Milliseconds device has been in disabled role. + uint64_t mDetachedTime; ///< Milliseconds device has been in detached role. + uint64_t mChildTime; ///< Milliseconds device has been in child role. + uint64_t mRouterTime; ///< Milliseconds device has been in router role. + uint64_t mLeaderTime; ///< Milliseconds device has been in leader role. +} otNetworkDiagMleCounters; + /** * Represents a Network Diagnostic Child Table Entry. * @@ -252,6 +282,7 @@ typedef struct otNetworkDiagTlv otNetworkDiagRoute mRoute; otLeaderData mLeaderData; otNetworkDiagMacCounters mMacCounters; + otNetworkDiagMleCounters mMleCounters; uint8_t mBatteryLevel; uint16_t mSupplyVoltage; uint32_t mMaxChildTimeout; diff --git a/src/cli/README.md b/src/cli/README.md index ec1f9efec..c066a1f5d 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -2059,6 +2059,91 @@ id:62 rloc16:0xf800 ext-addr:ce349873897233a5 ver:4 - br children: none ``` +### meshdiag childtable \ + +Start a query for child table of a router with a given RLOC16. + +Output lists all child entries. Information per child: + +- RLOC16 +- Extended MAC address +- Thread Version +- Timeout (in seconds) +- Age (seconds since last heard) +- Supervision interval (in seconds) +- Number of queued messages (in case the child is sleepy) +- Device Mode +- RSS (average and last) and link margin +- Error rates, frame tx (at MAC layer), IPv6 message tx (above MAC) +- Connection time (seconds since link establishment {dd}d.{hh}:{mm}:{ss} format) +- CSL info + - If synchronized + - Period (in unit of 10-symbols-time) + - Timeout (in seconds) + - Channel + +```bash +> meshdiag childtable 0x6400 +rloc16:0x6402 ext-addr:8e6f4d323bbed1fe ver:4 + timeout:120 age:36 supvn:129 q-msg:0 + rx-on:yes type:ftd full-net:yes + rss - ave:-20 last:-20 margin:80 + err-rate - frame:11.51% msg:0.76% + conn-time:00:11:07 + csl - sync:no period:0 timeout:0 channel:0 +rloc16:0x6403 ext-addr:ee24e64ecf8c079a ver:4 + timeout:120 age:19 supvn:129 q-msg:0 + rx-on:no type:mtd full-net:no + rss - ave:-20 last:-20 margin:80 + err-rate - frame:0.73% msg:0.00% + conn-time:01:08:53 + csl - sync:no period:0 timeout:0 channel:0 +Done +``` + +### meshdiag childip6 \ + +Send a query to a parent to retrieve the IPv6 addresses of all its MTD children. + +```bash +> meshdiag childip6 0xdc00 +child-rloc16: 0xdc02 + fdde:ad00:beef:0:ded8:cd58:b73:2c21 + fd00:2:0:0:c24a:456:3b6b:c597 + fd00:1:0:0:120b:95fe:3ecc:d238 +child-rloc16: 0xdc03 + fdde:ad00:beef:0:3aa6:b8bf:e7d6:eefe + fd00:2:0:0:8ff8:a188:7436:6720 + fd00:1:0:0:1fcf:5495:790a:370f +Done +``` + +### meshdiag routerneighbortable \ + +Start a query for router neighbor table of a router with a given RLOC16. + +Output lists all router neighbor entries. Information per entry: + +- RLOC16 +- Extended MAC address +- Thread Version +- RSS (average and last) and link margin +- Error rates, frame tx (at MAC layer), IPv6 message tx (above MAC) +- Connection time (seconds since link establishment {dd}d.{hh}:{mm}:{ss} format) + +```bash +> meshdiag routerneighbortable 0x7400 +rloc16:0x9c00 ext-addr:764788cf6e57a4d2 ver:4 + rss - ave:-20 last:-20 margin:80 + err-rate - frame:1.38% msg:0.00% + conn-time:01:54:02 +rloc16:0x7c00 ext-addr:4ed24fceec9bf6d3 ver:4 + rss - ave:-20 last:-20 margin:80 + err-rate - frame:0.72% msg:0.00% + conn-time:00:11:27 +Done +``` + ### mliid \ Set the Mesh Local IID. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 2e7316ef3..30e752846 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -5018,6 +5018,129 @@ template <> otError Interpreter::Process(Arg aArgs[]) SuccessOrExit(error = otMeshDiagDiscoverTopology(GetInstancePtr(), &config, HandleMeshDiagDiscoverDone, this)); error = OT_ERROR_PENDING; } + /** + * @cli meshdiag childtable + * @code + * meshdiag childtable 0x6400 + * rloc16:0x6402 ext-addr:8e6f4d323bbed1fe ver:4 + * timeout:120 age:36 supvn:129 q-msg:0 + * rx-on:yes type:ftd full-net:yes + * rss - ave:-20 last:-20 margin:80 + * err-rate - frame:11.51% msg:0.76% + * conn-time:00:11:07 + * csl - sync:no period:0 timeout:0 channel:0 + * rloc16:0x6403 ext-addr:ee24e64ecf8c079a ver:4 + * timeout:120 age:19 supvn:129 q-msg:0 + * rx-on:no type:mtd full-net:no + * rss - ave:-20 last:-20 margin:80 + * err-rate - frame:0.73% msg:0.00% + * conn-time:01:08:53 + * csl - sync:no period:0 timeout:0 channel:0 + * Done + * @endcode + * @par + * Start a query for child table of a router with a given RLOC16. + * Output lists all child entries. Information per child: + * - RLOC16 + * - Extended MAC address + * - Thread Version + * - Timeout (in seconds) + * - Age (seconds since last heard) + * - Supervision interval (in seconds) + * - Number of queued messages (in case child is sleepy) + * - Device Mode + * - RSS (average and last) + * - Error rates: frame tx (at MAC layer), IPv6 message tx (above MAC) + * - Connection time (seconds since link establishment `{dd}d.{hh}:{mm}:{ss}` format) + * - CSL info: + * - If synchronized + * - Period (in unit of 10-symbols-time) + * - Timeout (in seconds) + * + * @cparam meshdiag childtable @ca{router-rloc16} + * @sa otMeshDiagQueryChildTable + */ + else if (aArgs[0] == "childtable") + { + uint16_t routerRloc16; + + SuccessOrExit(error = aArgs[1].ParseAsUint16(routerRloc16)); + VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = otMeshDiagQueryChildTable(GetInstancePtr(), routerRloc16, + HandleMeshDiagQueryChildTableResult, this)); + + error = OT_ERROR_PENDING; + } + /** + * @cli meshdiag childip6 + * @code + * meshdiag childip6 0xdc00 + * child-rloc16: 0xdc02 + * fdde:ad00:beef:0:ded8:cd58:b73:2c21 + * fd00:2:0:0:c24a:456:3b6b:c597 + * fd00:1:0:0:120b:95fe:3ecc:d238 + * child-rloc16: 0xdc03 + * fdde:ad00:beef:0:3aa6:b8bf:e7d6:eefe + * fd00:2:0:0:8ff8:a188:7436:6720 + * fd00:1:0:0:1fcf:5495:790a:370f + * Done + * @endcode + * @par + * Send a query to a parent to retrieve the IPv6 addresses of all its MTD children. + * @cparam meshdiag childip6 @ca{parent-rloc16} + * @sa otMeshDiagQueryChildrenIp6Addrs + */ + else if (aArgs[0] == "childip6") + { + uint16_t parentRloc16; + + SuccessOrExit(error = aArgs[1].ParseAsUint16(parentRloc16)); + VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = otMeshDiagQueryChildrenIp6Addrs(GetInstancePtr(), parentRloc16, + HandleMeshDiagQueryChildIp6Addrs, this)); + + error = OT_ERROR_PENDING; + } + /** + * @cli meshdiag routerneighbortable + * @code + * meshdiag routerneighbortable 0x7400 + * rloc16:0x9c00 ext-addr:764788cf6e57a4d2 ver:4 + * rss - ave:-20 last:-20 margin:80 + * err-rate - frame:1.38% msg:0.00% + * conn-time:01:54:02 + * rloc16:0x7c00 ext-addr:4ed24fceec9bf6d3 ver:4 + * rss - ave:-20 last:-20 margin:80 + * err-rate - frame:0.72% msg:0.00% + * conn-time:00:11:27 + * Done + * @endcode + * @par + * Start a query for router neighbor table of a router with a given RLOC16. + * Output lists all router neighbor entries. Information per entry: + * - RLOC16 + * - Extended MAC address + * - Thread Version + * - RSS (average and last) and link margin + * - Error rates, frame tx (at MAC layer), IPv6 message tx (above MAC) + * - Connection time (seconds since link establishment `{dd}d.{hh}:{mm}:{ss}` format) + * @cparam meshdiag routerneighbortable @ca{router-rloc16} + * @sa otMeshDiagQueryRouterNeighborTable + */ + else if (aArgs[0] == "routerneighbortable") + { + uint16_t routerRloc16; + + SuccessOrExit(error = aArgs[1].ParseAsUint16(routerRloc16)); + VerifyOrExit(aArgs[2].IsEmpty(), error = OT_ERROR_INVALID_ARGS); + + SuccessOrExit(error = otMeshDiagQueryRouterNeighborTable(GetInstancePtr(), routerRloc16, + HandleMeshDiagQueryRouterNeighborTableResult, this)); + + error = OT_ERROR_PENDING; + } else { error = OT_ERROR_INVALID_COMMAND; @@ -5148,6 +5271,116 @@ exit: OutputResult(aError); } +void Interpreter::HandleMeshDiagQueryChildTableResult(otError aError, + const otMeshDiagChildEntry *aChildEntry, + void *aContext) +{ + reinterpret_cast(aContext)->HandleMeshDiagQueryChildTableResult(aError, aChildEntry); +} + +void Interpreter::HandleMeshDiagQueryChildTableResult(otError aError, const otMeshDiagChildEntry *aChildEntry) +{ + PercentageStringBuffer stringBuffer; + char string[OT_DURATION_STRING_SIZE]; + + VerifyOrExit(aChildEntry != nullptr); + + OutputFormat("rloc16:0x%04x ext-addr:", aChildEntry->mRloc16); + OutputExtAddress(aChildEntry->mExtAddress); + OutputLine(" ver:%u", aChildEntry->mVersion); + + OutputLine(kIndentSize, "timeout:%lu age:%lu supvn:%u q-msg:%u", ToUlong(aChildEntry->mTimeout), + ToUlong(aChildEntry->mAge), aChildEntry->mSupervisionInterval, aChildEntry->mQueuedMessageCount); + + OutputLine(kIndentSize, "rx-on:%s type:%s full-net:%s", aChildEntry->mRxOnWhenIdle ? "yes" : "no", + aChildEntry->mDeviceTypeFtd ? "ftd" : "mtd", aChildEntry->mFullNetData ? "yes" : "no"); + + OutputLine(kIndentSize, "rss - ave:%d last:%d margin:%d", aChildEntry->mAverageRssi, aChildEntry->mLastRssi, + aChildEntry->mLinkMargin); + + if (aChildEntry->mSupportsErrRate) + { + OutputFormat(kIndentSize, "err-rate - frame:%s%% ", + PercentageToString(aChildEntry->mFrameErrorRate, stringBuffer)); + OutputLine("msg:%s%% ", PercentageToString(aChildEntry->mMessageErrorRate, stringBuffer)); + } + + otConvertDurationInSecondsToString(aChildEntry->mConnectionTime, string, sizeof(string)); + OutputLine(kIndentSize, "conn-time:%s", string); + + OutputLine(kIndentSize, "csl - sync:%s period:%u timeout:%lu channel:%u", + aChildEntry->mCslSynchronized ? "yes" : "no", aChildEntry->mCslPeriod, ToUlong(aChildEntry->mCslTimeout), + aChildEntry->mCslChannel); + +exit: + OutputResult(aError); +} + +void Interpreter::HandleMeshDiagQueryRouterNeighborTableResult(otError aError, + const otMeshDiagRouterNeighborEntry *aNeighborEntry, + void *aContext) +{ + reinterpret_cast(aContext)->HandleMeshDiagQueryRouterNeighborTableResult(aError, aNeighborEntry); +} + +void Interpreter::HandleMeshDiagQueryRouterNeighborTableResult(otError aError, + const otMeshDiagRouterNeighborEntry *aNeighborEntry) +{ + PercentageStringBuffer stringBuffer; + char string[OT_DURATION_STRING_SIZE]; + + VerifyOrExit(aNeighborEntry != nullptr); + + OutputFormat("rloc16:0x%04x ext-addr:", aNeighborEntry->mRloc16); + OutputExtAddress(aNeighborEntry->mExtAddress); + OutputLine(" ver:%u", aNeighborEntry->mVersion); + + OutputLine(kIndentSize, "rss - ave:%d last:%d margin:%d", aNeighborEntry->mAverageRssi, aNeighborEntry->mLastRssi, + aNeighborEntry->mLinkMargin); + + if (aNeighborEntry->mSupportsErrRate) + { + OutputFormat(kIndentSize, "err-rate - frame:%s%% ", + PercentageToString(aNeighborEntry->mFrameErrorRate, stringBuffer)); + OutputLine("msg:%s%% ", PercentageToString(aNeighborEntry->mMessageErrorRate, stringBuffer)); + } + + otConvertDurationInSecondsToString(aNeighborEntry->mConnectionTime, string, sizeof(string)); + OutputLine(kIndentSize, "conn-time:%s", string); + +exit: + OutputResult(aError); +} + +void Interpreter::HandleMeshDiagQueryChildIp6Addrs(otError aError, + uint16_t aChildRloc16, + otMeshDiagIp6AddrIterator *aIp6AddrIterator, + void *aContext) +{ + reinterpret_cast(aContext)->HandleMeshDiagQueryChildIp6Addrs(aError, aChildRloc16, aIp6AddrIterator); +} + +void Interpreter::HandleMeshDiagQueryChildIp6Addrs(otError aError, + uint16_t aChildRloc16, + otMeshDiagIp6AddrIterator *aIp6AddrIterator) +{ + otIp6Address ip6Address; + + VerifyOrExit(aError == OT_ERROR_NONE || aError == OT_ERROR_PENDING); + VerifyOrExit(aIp6AddrIterator != nullptr); + + OutputLine("child-rloc16: 0x%04x", aChildRloc16); + + while (otMeshDiagGetNextIp6Address(aIp6AddrIterator, &ip6Address) == OT_ERROR_NONE) + { + OutputSpaces(kIndentSize); + OutputIp6AddressLine(ip6Address); + } + +exit: + OutputResult(aError); +} + #endif // OPENTHREAD_CONFIG_MESH_DIAG_ENABLE #endif // OPENTHREAD_FTD @@ -7198,6 +7431,10 @@ void Interpreter::HandleDiagnosticGetResponse(otError aError, OutputLine("MAC Counters:"); OutputNetworkDiagMacCounters(kIndentSize, diagTlv.mData.mMacCounters); break; + case OT_NETWORK_DIAGNOSTIC_TLV_MLE_COUNTERS: + OutputLine("MLE Counters:"); + OutputNetworkDiagMleCounters(kIndentSize, diagTlv.mData.mMleCounters); + break; case OT_NETWORK_DIAGNOSTIC_TLV_BATTERY_LEVEL: OutputLine("Battery Level: %u%%", diagTlv.mData.mBatteryLevel); break; @@ -7293,15 +7530,75 @@ void Interpreter::OutputLeaderData(uint8_t aIndentSize, const otLeaderData &aLea void Interpreter::OutputNetworkDiagMacCounters(uint8_t aIndentSize, const otNetworkDiagMacCounters &aMacCounters) { - OutputLine(aIndentSize, "IfInUnknownProtos: %lu", ToUlong(aMacCounters.mIfInUnknownProtos)); - OutputLine(aIndentSize, "IfInErrors: %lu", ToUlong(aMacCounters.mIfInErrors)); - OutputLine(aIndentSize, "IfOutErrors: %lu", ToUlong(aMacCounters.mIfOutErrors)); - OutputLine(aIndentSize, "IfInUcastPkts: %lu", ToUlong(aMacCounters.mIfInUcastPkts)); - OutputLine(aIndentSize, "IfInBroadcastPkts: %lu", ToUlong(aMacCounters.mIfInBroadcastPkts)); - OutputLine(aIndentSize, "IfInDiscards: %lu", ToUlong(aMacCounters.mIfInDiscards)); - OutputLine(aIndentSize, "IfOutUcastPkts: %lu", ToUlong(aMacCounters.mIfOutUcastPkts)); - OutputLine(aIndentSize, "IfOutBroadcastPkts: %lu", ToUlong(aMacCounters.mIfOutBroadcastPkts)); - OutputLine(aIndentSize, "IfOutDiscards: %lu", ToUlong(aMacCounters.mIfOutDiscards)); + struct CounterName + { + const uint32_t otNetworkDiagMacCounters::*mValuePtr; + const char *mName; + }; + + static const CounterName kCounterNames[] = { + {&otNetworkDiagMacCounters::mIfInUnknownProtos, "IfInUnknownProtos"}, + {&otNetworkDiagMacCounters::mIfInErrors, "IfInErrors"}, + {&otNetworkDiagMacCounters::mIfOutErrors, "IfOutErrors"}, + {&otNetworkDiagMacCounters::mIfInUcastPkts, "IfInUcastPkts"}, + {&otNetworkDiagMacCounters::mIfInBroadcastPkts, "IfInBroadcastPkts"}, + {&otNetworkDiagMacCounters::mIfInDiscards, "IfInDiscards"}, + {&otNetworkDiagMacCounters::mIfOutUcastPkts, "IfOutUcastPkts"}, + {&otNetworkDiagMacCounters::mIfOutBroadcastPkts, "IfOutBroadcastPkts"}, + {&otNetworkDiagMacCounters::mIfOutDiscards, "IfOutDiscards"}, + }; + + for (const CounterName &counter : kCounterNames) + { + OutputLine(aIndentSize, "%s: %lu", counter.mName, ToUlong(aMacCounters.*counter.mValuePtr)); + } +} + +void Interpreter::OutputNetworkDiagMleCounters(uint8_t aIndentSize, const otNetworkDiagMleCounters &aMleCounters) +{ + struct CounterName + { + const uint16_t otNetworkDiagMleCounters::*mValuePtr; + const char *mName; + }; + + struct TimeCounterName + { + const uint64_t otNetworkDiagMleCounters::*mValuePtr; + const char *mName; + }; + + static const CounterName kCounterNames[] = { + {&otNetworkDiagMleCounters::mDisabledRole, "DisabledRole"}, + {&otNetworkDiagMleCounters::mDetachedRole, "DetachedRole"}, + {&otNetworkDiagMleCounters::mChildRole, "ChildRole"}, + {&otNetworkDiagMleCounters::mRouterRole, "RouterRole"}, + {&otNetworkDiagMleCounters::mLeaderRole, "LeaderRole"}, + {&otNetworkDiagMleCounters::mAttachAttempts, "AttachAttempts"}, + {&otNetworkDiagMleCounters::mPartitionIdChanges, "PartitionIdChanges"}, + {&otNetworkDiagMleCounters::mBetterPartitionAttachAttempts, "BetterPartitionAttachAttempts"}, + {&otNetworkDiagMleCounters::mParentChanges, "ParentChanges"}, + }; + + static const TimeCounterName kTimeCounterNames[] = { + {&otNetworkDiagMleCounters::mTrackedTime, "TrackedTime"}, + {&otNetworkDiagMleCounters::mDisabledTime, "DisabledTime"}, + {&otNetworkDiagMleCounters::mDetachedTime, "DetachedTime"}, + {&otNetworkDiagMleCounters::mChildTime, "ChildTime"}, + {&otNetworkDiagMleCounters::mRouterTime, "RouterTime"}, + {&otNetworkDiagMleCounters::mLeaderTime, "LeaderTime"}, + }; + + for (const CounterName &counter : kCounterNames) + { + OutputLine(aIndentSize, "%s: %u", counter.mName, aMleCounters.*counter.mValuePtr); + } + + for (const TimeCounterName &counter : kTimeCounterNames) + { + OutputFormat("%s: ", counter.mName); + OutputUint64Line(aMleCounters.*counter.mValuePtr); + } } void Interpreter::OutputChildTableEntry(uint8_t aIndentSize, const otNetworkDiagChildEntry &aChildEntry) diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 2a79c7442..54b0346c4 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -428,6 +428,23 @@ private: #if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD static void HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo, void *aContext); void HandleMeshDiagDiscoverDone(otError aError, otMeshDiagRouterInfo *aRouterInfo); + static void HandleMeshDiagQueryChildTableResult(otError aError, + const otMeshDiagChildEntry *aChildEntry, + void *aContext); + void HandleMeshDiagQueryChildTableResult(otError aError, const otMeshDiagChildEntry *aChildEntry); + static void HandleMeshDiagQueryChildIp6Addrs(otError aError, + uint16_t aChildRloc16, + otMeshDiagIp6AddrIterator *aIp6AddrIterator, + void *aContext); + void HandleMeshDiagQueryChildIp6Addrs(otError aError, + uint16_t aChildRloc16, + otMeshDiagIp6AddrIterator *aIp6AddrIterator); + static void HandleMeshDiagQueryRouterNeighborTableResult(otError aError, + const otMeshDiagRouterNeighborEntry *aNeighborEntry, + void *aContext); + void HandleMeshDiagQueryRouterNeighborTableResult(otError aError, + const otMeshDiagRouterNeighborEntry *aNeighborEntry); + #endif #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_TMF_PROXY_MLR_ENABLE && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE static void HandleMlrRegResult(void *aContext, @@ -465,6 +482,7 @@ private: void OutputRouteData(uint8_t aIndentSize, const otNetworkDiagRouteData &aRouteData); void OutputLeaderData(uint8_t aIndentSize, const otLeaderData &aLeaderData); void OutputNetworkDiagMacCounters(uint8_t aIndentSize, const otNetworkDiagMacCounters &aMacCounters); + void OutputNetworkDiagMleCounters(uint8_t aIndentSize, const otNetworkDiagMleCounters &aMleCounters); void OutputChildTableEntry(uint8_t aIndentSize, const otNetworkDiagChildEntry &aChildEntry); #endif diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 8c9aaa4e2..1b3297074 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -668,6 +668,7 @@ openthread_core_files = [ "thread/network_data_types.hpp", "thread/network_diagnostic.cpp", "thread/network_diagnostic.hpp", + "thread/network_diagnostic_tlvs.cpp", "thread/network_diagnostic_tlvs.hpp", "thread/panid_query_server.cpp", "thread/panid_query_server.hpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index acd33ffe6..e77b576e1 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -226,6 +226,7 @@ set(COMMON_SOURCES thread/network_data_tlvs.cpp thread/network_data_types.cpp thread/network_diagnostic.cpp + thread/network_diagnostic_tlvs.cpp thread/panid_query_server.cpp thread/radio_selector.cpp thread/router_table.cpp diff --git a/src/core/api/mesh_diag_api.cpp b/src/core/api/mesh_diag_api.cpp index 888476a1a..cfade70f6 100644 --- a/src/core/api/mesh_diag_api.cpp +++ b/src/core/api/mesh_diag_api.cpp @@ -64,4 +64,28 @@ otError otMeshDiagGetNextChildInfo(otMeshDiagChildIterator *aIterator, otMeshDia return AsCoreType(aIterator).GetNextChildInfo(AsCoreType(aChildInfo)); } +otError otMeshDiagQueryChildTable(otInstance *aInstance, + uint16_t aRloc16, + otMeshDiagQueryChildTableCallback aCallback, + void *aContext) +{ + return AsCoreType(aInstance).Get().QueryChildTable(aRloc16, aCallback, aContext); +} + +otError otMeshDiagQueryChildrenIp6Addrs(otInstance *aInstance, + uint16_t aRloc16, + otMeshDiagChildIp6AddrsCallback aCallback, + void *aContext) +{ + return AsCoreType(aInstance).Get().QueryChildrenIp6Addrs(aRloc16, aCallback, aContext); +} + +otError otMeshDiagQueryRouterNeighborTable(otInstance *aInstance, + uint16_t aRloc16, + otMeshDiagQueryRouterNeighborTableCallback aCallback, + void *aContext) +{ + return AsCoreType(aInstance).Get().QueryRouterNeighborTable(aRloc16, aCallback, aContext); +} + #endif // OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD diff --git a/src/core/common/tlvs.cpp b/src/core/common/tlvs.cpp index 01582059c..a5ed15b45 100644 --- a/src/core/common/tlvs.cpp +++ b/src/core/common/tlvs.cpp @@ -57,6 +57,13 @@ const uint8_t *Tlv::GetValue(void) const Error Tlv::AppendTo(Message &aMessage) const { return aMessage.AppendBytes(this, static_cast(GetSize())); } Error Tlv::FindTlv(const Message &aMessage, uint8_t aType, uint16_t aMaxSize, Tlv &aTlv) +{ + uint16_t offset; + + return FindTlv(aMessage, aType, aMaxSize, aTlv, offset); +} + +Error Tlv::FindTlv(const Message &aMessage, uint8_t aType, uint16_t aMaxSize, Tlv &aTlv, uint16_t &aOffset) { Error error; ParsedInfo info; @@ -69,11 +76,11 @@ Error Tlv::FindTlv(const Message &aMessage, uint8_t aType, uint16_t aMaxSize, Tl } aMessage.ReadBytes(info.mOffset, &aTlv, aMaxSize); + aOffset = info.mOffset; exit: return error; } - Error Tlv::FindTlvValueOffset(const Message &aMessage, uint8_t aType, uint16_t &aValueOffset, uint16_t &aLength) { Error error; diff --git a/src/core/common/tlvs.hpp b/src/core/common/tlvs.hpp index bbfc994cf..79bd5bd62 100644 --- a/src/core/common/tlvs.hpp +++ b/src/core/common/tlvs.hpp @@ -263,6 +263,23 @@ public: */ static Error FindTlv(const Message &aMessage, uint8_t aType, uint16_t aMaxSize, Tlv &aTlv); + /** + * Searches for and reads a requested TLV out of a given message. + * + * Can be used independent of whether the read TLV (from message) is an Extended TLV or not. + * + * @param[in] aMessage A reference to the message. + * @param[in] aType The Type value to search for. + * @param[in] aMaxSize Maximum number of bytes to read. + * @param[out] aTlv A reference to the TLV that will be copied to. + * @param[out] aOffset A reference to return the offset to start of the TLV in @p aMessage. + * + * @retval kErrorNone Successfully copied the TLV. + * @retval kErrorNotFound Could not find the TLV with Type @p aType. + * + */ + static Error FindTlv(const Message &aMessage, uint8_t aType, uint16_t aMaxSize, Tlv &aTlv, uint16_t &aOffset); + /** * Searches for and reads a requested TLV out of a given message. * @@ -282,6 +299,26 @@ public: return FindTlv(aMessage, TlvType::kType, sizeof(TlvType), aTlv); } + /** + * Searches for and reads a requested TLV out of a given message. + * + * Can be used independent of whether the read TLV (from message) is an Extended TLV or not. + * + * @tparam TlvType The TlvType to search for (must be a sub-class of `Tlv`). + * + * @param[in] aMessage A reference to the message. + * @param[out] aTlv A reference to the TLV that will be copied to. + * @param[out] aOffset A reference to return the offset to start of the TLV in @p aMessage. + * + * @retval kErrorNone Successfully copied the TLV. + * @retval kErrorNotFound Could not find the TLV with Type @p aType. + * + */ + template static Error FindTlv(const Message &aMessage, TlvType &aTlv, uint16_t &aOffset) + { + return FindTlv(aMessage, TlvType::kType, sizeof(TlvType), aTlv, aOffset); + } + /** * Finds the offset and length of TLV value for a given TLV type within @p aMessage. * diff --git a/src/core/thread/network_diagnostic.cpp b/src/core/thread/network_diagnostic.cpp index 547c80a62..be009f396 100644 --- a/src/core/thread/network_diagnostic.cpp +++ b/src/core/thread/network_diagnostic.cpp @@ -42,6 +42,7 @@ #include "common/instance.hpp" #include "common/locator_getters.hpp" #include "common/log.hpp" +#include "common/random.hpp" #include "mac/mac.hpp" #include "net/netif.hpp" #include "thread/mesh_forwarder.hpp" @@ -401,11 +402,7 @@ exit: template <> void Server::HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { - Error error = kErrorNone; - Coap::Message *response = nullptr; - Tmf::MessageInfo responseInfo(GetInstance()); - - VerifyOrExit(aMessage.IsPostRequest(), error = kErrorDrop); + VerifyOrExit(aMessage.IsPostRequest()); LogInfo("Received %s from %s", UriToString(), aMessageInfo.GetPeerAddr().ToString().AsCString()); @@ -416,18 +413,377 @@ void Server::HandleTmf(Coap::Message &aMessage, const Ip IgnoreError(Get().SendEmptyAck(aMessage, aMessageInfo)); } - response = Get().NewConfirmablePostMessage(kUriDiagnosticGetAnswer); - VerifyOrExit(response != nullptr, error = kErrorNoBufs); - - SuccessOrExit(error = AppendRequestedTlvs(aMessage, *response)); - - PrepareMessageInfoForDest(aMessageInfo.GetPeerAddr(), responseInfo); - SuccessOrExit(error = Get().SendMessage(*response, responseInfo)); +#if OPENTHREAD_MTD + SendAnswer(aMessageInfo.GetPeerAddr(), aMessage); +#elif OPENTHREAD_FTD + PrepareAndSendAnswers(aMessageInfo.GetPeerAddr(), aMessage); +#endif exit: - FreeMessageOnError(response, error); + return; } +#if OPENTHREAD_MTD + +void Server::SendAnswer(const Ip6::Address &aDestination, const Message &aRequest) +{ + Error error = kErrorNone; + Coap::Message *answer = nullptr; + Tmf::MessageInfo messageInfo(GetInstance()); + AnswerTlv answerTlv; + uint16_t queryId; + + answer = Get().NewConfirmablePostMessage(kUriDiagnosticGetAnswer); + VerifyOrExit(answer != nullptr, error = kErrorNoBufs); + + IgnoreError(answer->SetPriority(aRequest.GetPriority())); + + if (Tlv::Find(aRequest, queryId) == kErrorNone) + { + SuccessOrExit(error = Tlv::Append(*answer, queryId)); + } + + SuccessOrExit(error = AppendRequestedTlvs(aRequest, *answer)); + + answerTlv.Init(0, /* aIsLast */ true); + SuccessOrExit(answer->Append(answerTlv)); + + PrepareMessageInfoForDest(aDestination, messageInfo); + + error = Get().SendMessage(*answer, messageInfo); + +exit: + FreeMessageOnError(answer, error); +} + +#endif // OPENTHREAD_MTD + +#if OPENTHREAD_FTD + +Error Server::AllocateAnswer(Coap::Message *&aAnswer, AnswerInfo &aInfo) +{ + // Allocate an `Answer` message, adds it in `mAnswerQueue`, + // update the `aInfo.mFirstAnswer` if it is the first allocated + // messages, and appends `QueryIdTlv` to the message (if needed). + + Error error = kErrorNone; + + aAnswer = Get().NewConfirmablePostMessage(kUriDiagnosticGetAnswer); + VerifyOrExit(aAnswer != nullptr, error = kErrorNoBufs); + IgnoreError(aAnswer->SetPriority(aInfo.mPriority)); + + mAnswerQueue.Enqueue(*aAnswer); + + if (aInfo.mFirstAnswer == nullptr) + { + aInfo.mFirstAnswer = aAnswer; + } + + if (aInfo.mHasQueryId) + { + SuccessOrExit(error = Tlv::Append(*aAnswer, aInfo.mQueryId)); + } + +exit: + return error; +} + +bool Server::IsLastAnswer(const Coap::Message &aAnswer) const +{ + // Indicates whether `aAnswer` is the last one associated with + // the same query. + + bool isLast = true; + AnswerTlv answerTlv; + + // If there is no Answer TLV, we assume it is the last answer. + + SuccessOrExit(Tlv::FindTlv(aAnswer, answerTlv)); + isLast = answerTlv.IsLast(); + +exit: + return isLast; +} + +void Server::FreeAllRelatedAnswers(Coap::Message &aFirstAnswer) +{ + // This method dequeues and frees all answer messages related to + // same query as `aFirstAnswer`. Note that related answers are + // enqueued in order. + + Coap::Message *answer = &aFirstAnswer; + + while (answer != nullptr) + { + Coap::Message *next = IsLastAnswer(*answer) ? nullptr : answer->GetNextCoapMessage(); + + mAnswerQueue.DequeueAndFree(*answer); + answer = next; + } +} + +void Server::PrepareAndSendAnswers(const Ip6::Address &aDestination, const Message &aRequest) +{ + Coap::Message *answer; + Error error; + AnswerInfo info; + uint16_t offset; + uint16_t length; + uint16_t endOffset; + AnswerTlv answerTlv; + + if (Tlv::Find(aRequest, info.mQueryId) == kErrorNone) + { + info.mHasQueryId = true; + } + + info.mPriority = aRequest.GetPriority(); + + SuccessOrExit(error = AllocateAnswer(answer, info)); + + SuccessOrExit(error = Tlv::FindTlvValueOffset(aRequest, Tlv::kTypeList, offset, length)); + endOffset = offset + length; + + for (; offset < endOffset; offset++) + { + uint8_t tlvType; + + SuccessOrExit(error = aRequest.Read(offset, tlvType)); + + switch (tlvType) + { + case ChildTlv::kType: + SuccessOrExit(error = AppendChildTableAsChildTlvs(answer, info)); + break; + case ChildIp6AddressListTlv::kType: + SuccessOrExit(error = AppendChildTableIp6AddressList(answer, info)); + break; + case RouterNeighborTlv::kType: + SuccessOrExit(error = AppendRouterNeighborTlvs(answer, info)); + break; + default: + SuccessOrExit(error = AppendDiagTlv(tlvType, *answer)); + break; + } + + SuccessOrExit(error = CheckAnswerLength(answer, info)); + } + + answerTlv.Init(info.mAnswerIndex, /* aIsLast */ true); + SuccessOrExit(error = answer->Append(answerTlv)); + + SendNextAnswer(*info.mFirstAnswer, aDestination); + +exit: + if ((error != kErrorNone) && (info.mFirstAnswer != nullptr)) + { + FreeAllRelatedAnswers(*info.mFirstAnswer); + } +} + +Error Server::CheckAnswerLength(Coap::Message *&aAnswer, AnswerInfo &aInfo) +{ + // This method checks the length of the `aAnswer` message and if it + // is above the threshold, it enqueues the message for transmission + // after appending an Answer TLV with the current index to the + // message. In this case, it will also allocate a new answer + // message. + + Error error = kErrorNone; + AnswerTlv answerTlv; + + VerifyOrExit(aAnswer->GetLength() >= kAnswerMessageLengthThreshold); + + answerTlv.Init(aInfo.mAnswerIndex++, /* aIsLast */ false); + SuccessOrExit(error = aAnswer->Append(answerTlv)); + + error = AllocateAnswer(aAnswer, aInfo); + +exit: + return error; +} + +void Server::SendNextAnswer(Coap::Message &aAnswer, const Ip6::Address &aDestination) +{ + // This method send the given next `aAnswer` associated with + // a query to the `aDestination`. + + Error error = kErrorNone; + Coap::Message *nextAnswer = IsLastAnswer(aAnswer) ? nullptr : aAnswer.GetNextCoapMessage(); + Tmf::MessageInfo messageInfo(GetInstance()); + + mAnswerQueue.Dequeue(aAnswer); + + PrepareMessageInfoForDest(aDestination, messageInfo); + + // When sending the message, we pass `nextAnswer` as `aContext` + // to be used when invoking callback `HandleAnswerResponse()`. + + error = Get().SendMessage(aAnswer, messageInfo, HandleAnswerResponse, nextAnswer); + + if (error != kErrorNone) + { + // If the `SendMessage()` fails, we `Free` the dequeued + // `aAnswer` and all the related next answers in the queue. + + aAnswer.Free(); + + if (nextAnswer != nullptr) + { + FreeAllRelatedAnswers(*nextAnswer); + } + } +} + +void Server::HandleAnswerResponse(void *aContext, otMessage *aMessage, const otMessageInfo *aMessageInfo, Error aResult) +{ + Coap::Message *nextAnswer = static_cast(aContext); + + VerifyOrExit(nextAnswer != nullptr); + + nextAnswer->Get().HandleAnswerResponse(*nextAnswer, AsCoapMessagePtr(aMessage), AsCoreTypePtr(aMessageInfo), + aResult); + +exit: + return; +} + +void Server::HandleAnswerResponse(Coap::Message &aNextAnswer, + Coap::Message *aResponse, + const Ip6::MessageInfo *aMessageInfo, + Error aResult) +{ + Error error = aResult; + + SuccessOrExit(error); + VerifyOrExit(aResponse != nullptr && aMessageInfo != nullptr, error = kErrorDrop); + VerifyOrExit(aResponse->GetCode() == Coap::kCodeChanged, error = kErrorDrop); + + SendNextAnswer(aNextAnswer, aMessageInfo->GetPeerAddr()); + +exit: + if (error != kErrorNone) + { + FreeAllRelatedAnswers(aNextAnswer); + } +} + +Error Server::AppendChildTableAsChildTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo) +{ + Error error = kErrorNone; + ChildTlv childTlv; + + for (Child &child : Get().Iterate(Child::kInStateValid)) + { + childTlv.InitFrom(child); + + SuccessOrExit(error = childTlv.AppendTo(*aAnswer)); + SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo)); + } + + // Add empty TLV to indicate end of the list + + childTlv.InitAsEmpty(); + SuccessOrExit(error = childTlv.AppendTo(*aAnswer)); + +exit: + return error; +} + +Error Server::AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo) +{ + Error error = kErrorNone; + RouterNeighborTlv neighborTlv; + + for (Router &router : Get()) + { + if (!router.IsStateValid()) + { + continue; + } + + neighborTlv.InitFrom(router); + + SuccessOrExit(error = neighborTlv.AppendTo(*aAnswer)); + SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo)); + } + + // Add empty TLV to indicate end of the list + + neighborTlv.InitAsEmpty(); + SuccessOrExit(error = neighborTlv.AppendTo(*aAnswer)); + +exit: + return error; +} + +Error Server::AppendChildTableIp6AddressList(Coap::Message *&aAnswer, AnswerInfo &aInfo) +{ + Error error = kErrorNone; + Tlv tlv; + + for (const Child &child : Get().Iterate(Child::kInStateValid)) + { + SuccessOrExit(error = AppendChildIp6AddressListTlv(*aAnswer, child)); + SuccessOrExit(error = CheckAnswerLength(aAnswer, aInfo)); + } + + // Add empty TLV to indicate end of the list + + tlv.SetType(Tlv::kChildIp6AddressList); + tlv.SetLength(0); + SuccessOrExit(error = aAnswer->Append(tlv)); + +exit: + return error; +} + +Error Server::AppendChildIp6AddressListTlv(Coap::Message &aAnswer, const Child &aChild) +{ + Error error = kErrorNone; + uint16_t numIp6Addr = 0; + ChildIp6AddressListTlvValue tlvValue; + + for (const Ip6::Address &address : aChild.IterateIp6Addresses()) + { + OT_UNUSED_VARIABLE(address); + numIp6Addr++; + } + + VerifyOrExit(numIp6Addr > 0); + + if ((numIp6Addr * sizeof(Ip6::Address) + sizeof(ChildIp6AddressListTlvValue)) <= Tlv::kBaseTlvMaxLength) + { + Tlv tlv; + + tlv.SetType(Tlv::kChildIp6AddressList); + tlv.SetLength(static_cast(numIp6Addr * sizeof(Ip6::Address) + sizeof(ChildIp6AddressListTlvValue))); + SuccessOrExit(error = aAnswer.Append(tlv)); + } + else + { + ExtendedTlv extTlv; + + extTlv.SetType(Tlv::kChildIp6AddressList); + extTlv.SetLength(numIp6Addr * sizeof(Ip6::Address) + sizeof(ChildIp6AddressListTlvValue)); + SuccessOrExit(error = aAnswer.Append(extTlv)); + } + + tlvValue.SetRloc16(aChild.GetRloc16()); + + SuccessOrExit(error = aAnswer.Append(tlvValue)); + + for (const Ip6::Address &address : aChild.IterateIp6Addresses()) + { + SuccessOrExit(error = aAnswer.Append(address)); + } + +exit: + return error; +} + +#endif // OPENTHREAD_FTD + template <> void Server::HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) { @@ -442,6 +798,7 @@ void Server::HandleTmf(Coap::Message &aMessage, const response = Get().NewResponseMessage(aMessage); VerifyOrExit(response != nullptr, error = kErrorNoBufs); + IgnoreError(response->SetPriority(aMessage.GetPriority())); SuccessOrExit(error = AppendRequestedTlvs(aMessage, *response)); SuccessOrExit(error = Get().SendMessage(*response, aMessageInfo)); @@ -476,6 +833,10 @@ template <> void Server::HandleTmf(Coap::Message &aMessage, Get().ResetCounters(); break; + case Tlv::kMleCounters: + Get().ResetCounters(); + break; + default: break; } @@ -494,6 +855,7 @@ exit: Client::Client(Instance &aInstance) : InstanceLocator(aInstance) + , mQueryId(Random::NonCrypto::GetUint16()) { } @@ -507,11 +869,12 @@ Error Client::SendDiagnosticGet(const Ip6::Address &aDestination, if (aDestination.IsMulticast()) { - error = SendCommand(kUriDiagnosticGetQuery, aDestination, aTlvTypes, aCount); + error = SendCommand(kUriDiagnosticGetQuery, Message::kPriorityNormal, aDestination, aTlvTypes, aCount); } else { - error = SendCommand(kUriDiagnosticGetRequest, aDestination, aTlvTypes, aCount, &HandleGetResponse, this); + error = SendCommand(kUriDiagnosticGetRequest, Message::kPriorityNormal, aDestination, aTlvTypes, aCount, + &HandleGetResponse, this); } SuccessOrExit(error); @@ -523,6 +886,7 @@ exit: } Error Client::SendCommand(Uri aUri, + Message::Priority aPriority, const Ip6::Address &aDestination, const uint8_t aTlvTypes[], uint8_t aCount, @@ -549,12 +913,18 @@ Error Client::SendCommand(Uri aUri, } VerifyOrExit(message != nullptr, error = kErrorNoBufs); + IgnoreError(message->SetPriority(aPriority)); if (aCount > 0) { SuccessOrExit(error = Tlv::Append(*message, aTlvTypes, aCount)); } + if (aUri == kUriDiagnosticGetQuery) + { + SuccessOrExit(error = Tlv::Append(*message, ++mQueryId)); + } + Get().PrepareMessageInfoForDest(aDestination, messageInfo); SuccessOrExit(error = Get().SendMessage(*message, messageInfo, aHandler, aContext)); @@ -589,7 +959,13 @@ void Client::HandleTmf(Coap::Message &aMessage, const I LogInfo("Received %s from %s", ot::UriToString(), aMessageInfo.GetPeerAddr().ToString().AsCString()); - mGetCallback.InvokeIfSet(kErrorNone, &aMessage, &aMessageInfo); +#if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD + // Let the `MeshDiag` process the message first. + if (!Get().HandleDiagnosticGetAnswer(aMessage, aMessageInfo)) +#endif + { + mGetCallback.InvokeIfSet(kErrorNone, &aMessage, &aMessageInfo); + } IgnoreError(Get().SendEmptyAck(aMessage, aMessageInfo)); @@ -599,7 +975,7 @@ exit: Error Client::SendDiagnosticReset(const Ip6::Address &aDestination, const uint8_t aTlvTypes[], uint8_t aCount) { - return SendCommand(kUriDiagnosticReset, aDestination, aTlvTypes, aCount); + return SendCommand(kUriDiagnosticReset, Message::kPriorityNormal, aDestination, aTlvTypes, aCount); } static void ParseRoute(const RouteTlv &aRouteTlv, otNetworkDiagRoute &aNetworkDiagRoute) @@ -770,6 +1146,16 @@ Error Client::GetNextDiagTlv(const Coap::Message &aMessage, Iterator &aIterator, break; } + case Tlv::kMleCounters: + { + MleCountersTlv mleCoutersTlv; + + SuccessOrExit(error = aMessage.Read(offset, mleCoutersTlv)); + VerifyOrExit(mleCoutersTlv.IsValid(), error = kErrorParse); + mleCoutersTlv.Read(aTlvInfo.mData.mMleCounters); + break; + } + case Tlv::kBatteryLevel: SuccessOrExit(error = Tlv::Read(aMessage, offset, aTlvInfo.mData.mBatteryLevel)); break; diff --git a/src/core/thread/network_diagnostic.hpp b/src/core/thread/network_diagnostic.hpp index 0070207b4..f610f2bb0 100644 --- a/src/core/thread/network_diagnostic.hpp +++ b/src/core/thread/network_diagnostic.hpp @@ -48,6 +48,10 @@ namespace ot { +namespace Utils { +class MeshDiag; +} + namespace NetworkDiagnostic { /** @@ -144,7 +148,27 @@ public: #endif // OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE private: - static constexpr uint16_t kMaxChildEntries = 398; + static constexpr uint16_t kMaxChildEntries = 398; + static constexpr uint16_t kAnswerMessageLengthThreshold = 800; + +#if OPENTHREAD_FTD + struct AnswerInfo + { + AnswerInfo(void) + : mAnswerIndex(0) + , mQueryId(0) + , mHasQueryId(false) + , mFirstAnswer(nullptr) + { + } + + uint16_t mAnswerIndex; + uint16_t mQueryId; + bool mHasQueryId; + Message::Priority mPriority; + Coap::Message *mFirstAnswer; + }; +#endif static const char kVendorName[]; static const char kVendorModel[]; @@ -153,10 +177,34 @@ private: Error AppendDiagTlv(uint8_t aTlvType, Message &aMessage); Error AppendIp6AddressList(Message &aMessage); Error AppendMacCounters(Message &aMessage); - Error AppendChildTable(Message &aMessage); Error AppendRequestedTlvs(const Message &aRequest, Message &aResponse); void PrepareMessageInfoForDest(const Ip6::Address &aDestination, Tmf::MessageInfo &aMessageInfo) const; +#if OPENTHREAD_MTD + void SendAnswer(const Ip6::Address &aDestination, const Message &aRequest); +#elif OPENTHREAD_FTD + Error AllocateAnswer(Coap::Message *&aAnswer, AnswerInfo &aInfo); + Error CheckAnswerLength(Coap::Message *&aAnswer, AnswerInfo &aInfo); + bool IsLastAnswer(const Coap::Message &aAnswer) const; + void FreeAllRelatedAnswers(Coap::Message &aFirstAnswer); + void PrepareAndSendAnswers(const Ip6::Address &aDestination, const Message &aRequest); + void SendNextAnswer(Coap::Message &aAnswer, const Ip6::Address &aDestination); + Error AppendChildTable(Message &aMessage); + Error AppendChildTableAsChildTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo); + Error AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo); + Error AppendChildTableIp6AddressList(Coap::Message *&aAnswer, AnswerInfo &aInfo); + Error AppendChildIp6AddressListTlv(Coap::Message &aAnswer, const Child &aChild); + + static void HandleAnswerResponse(void *aContext, + otMessage *aMessage, + const otMessageInfo *aMessageInfo, + Error aResult); + void HandleAnswerResponse(Coap::Message &aNextAnswer, + Coap::Message *aResponse, + const Ip6::MessageInfo *aMessageInfo, + Error aResult); +#endif + template void HandleTmf(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); #if OPENTHREAD_CONFIG_NET_DIAG_VENDOR_INFO_SET_API_ENABLE @@ -166,6 +214,10 @@ private: VendorModelTlv::StringType mVendorModel; VendorSwVersionTlv::StringType mVendorSwVersion; #endif + +#if OPENTHREAD_FTD + Coap::MessageQueue mAnswerQueue; +#endif }; DeclareTmfHandler(Server, kUriDiagnosticGetRequest); @@ -181,6 +233,7 @@ DeclareTmfHandler(Server, kUriDiagnosticGetAnswer); class Client : public InstanceLocator, private NonCopyable { friend class Tmf::Agent; + friend class Utils::MeshDiag; public: typedef otNetworkDiagIterator Iterator; ///< Iterator to go through TLVs in `GetNextDiagTlv()`. @@ -239,8 +292,17 @@ public: */ static Error GetNextDiagTlv(const Coap::Message &aMessage, Iterator &aIterator, TlvInfo &aTlvInfo); + /** + * This method returns the query ID used for the last Network Diagnostic Query command. + * + * @returns The query ID used for last query. + * + */ + uint16_t GetLastQueryId(void) const { return mQueryId; } + private: Error SendCommand(Uri aUri, + Message::Priority aPriority, const Ip6::Address &aDestination, const uint8_t aTlvTypes[], uint8_t aCount, @@ -259,6 +321,7 @@ private: static const char *UriToString(Uri aUri); #endif + uint16_t mQueryId; Callback mGetCallback; }; diff --git a/src/core/thread/network_diagnostic_tlvs.cpp b/src/core/thread/network_diagnostic_tlvs.cpp new file mode 100644 index 000000000..941b0ccef --- /dev/null +++ b/src/core/thread/network_diagnostic_tlvs.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2023, 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 methods for generating and processing Network Diagnostics TLVs. + */ + +#include "network_diagnostic_tlvs.hpp" + +#include "common/time.hpp" + +namespace ot { +namespace NetworkDiagnostic { + +using ot::Encoding::BigEndian::HostSwap16; +using ot::Encoding::BigEndian::HostSwap32; +using ot::Encoding::BigEndian::HostSwap64; + +#if OPENTHREAD_FTD + +void ChildTlv::InitFrom(const Child &aChild) +{ + Clear(); + + SetType(kChild); + SetLength(sizeof(*this) - sizeof(Tlv)); + + mFlags |= aChild.IsRxOnWhenIdle() ? kFlagsRxOnWhenIdle : 0; + mFlags |= aChild.IsFullThreadDevice() ? kFlagsFtd : 0; + mFlags |= (aChild.GetNetworkDataType() == NetworkData::kFullSet) ? kFlagsFullNetdta : 0; + mFlags |= kFlagsTrackErrRate; + + mRloc16 = HostSwap16(aChild.GetRloc16()); + mExtAddress = aChild.GetExtAddress(); + mVersion = HostSwap16(aChild.GetVersion()); + mTimeout = HostSwap32(aChild.GetTimeout()); + mAge = HostSwap32(Time::MsecToSec(TimerMilli::GetNow() - aChild.GetLastHeard())); + mConnectionTime = HostSwap32(aChild.GetConnectionTime()); + mSupervisionInterval = HostSwap16(aChild.GetSupervisionInterval()); + mLinkMargin = aChild.GetLinkInfo().GetLinkMargin(); + mAverageRssi = aChild.GetLinkInfo().GetAverageRss(); + mLastRssi = aChild.GetLinkInfo().GetLastRss(); + mFrameErrorRate = HostSwap16(aChild.GetLinkInfo().GetFrameErrorRate()); + mMessageErrorRate = HostSwap16(aChild.GetLinkInfo().GetMessageErrorRate()); + mQueuedMessageCount = HostSwap16(aChild.GetIndirectMessageCount()); + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + mFlags |= aChild.IsCslSynchronized() ? kFlagsCslSync : 0; + mCslPeriod = HostSwap16(aChild.GetCslPeriod()); + mCslTimeout = HostSwap32(aChild.GetCslTimeout()); + mCslChannel = aChild.GetCslChannel(); +#endif +} + +void RouterNeighborTlv::InitFrom(const Router &aRouter) +{ + Clear(); + + SetType(kRouterNeighbor); + SetLength(sizeof(*this) - sizeof(Tlv)); + + mFlags |= kFlagsTrackErrRate; + mRloc16 = HostSwap16(aRouter.GetRloc16()); + mExtAddress = aRouter.GetExtAddress(); + mVersion = HostSwap16(aRouter.GetVersion()); + mConnectionTime = HostSwap32(aRouter.GetConnectionTime()); + mLinkMargin = aRouter.GetLinkInfo().GetLinkMargin(); + mAverageRssi = aRouter.GetLinkInfo().GetAverageRss(); + mLastRssi = aRouter.GetLinkInfo().GetLastRss(); + mFrameErrorRate = HostSwap16(aRouter.GetLinkInfo().GetFrameErrorRate()); + mMessageErrorRate = HostSwap16(aRouter.GetLinkInfo().GetMessageErrorRate()); +} + +#endif // OPENTHREAD_FTD + +void AnswerTlv::Init(uint16_t aIndex, bool aIsLast) +{ + SetType(kAnswer); + SetLength(sizeof(*this) - sizeof(Tlv)); + + SetFlagsIndex((aIndex & kIndexMask) | (aIsLast ? kIsLastFlag : 0)); +} + +void MleCountersTlv::Init(const Mle::Counters &aMleCounters) +{ + SetType(kMleCounters); + SetLength(sizeof(*this) - sizeof(Tlv)); + + mDisabledRole = HostSwap16(aMleCounters.mDisabledRole); + mDetachedRole = HostSwap16(aMleCounters.mDetachedRole); + mChildRole = HostSwap16(aMleCounters.mChildRole); + mRouterRole = HostSwap16(aMleCounters.mRouterRole); + mLeaderRole = HostSwap16(aMleCounters.mLeaderRole); + mAttachAttempts = HostSwap16(aMleCounters.mAttachAttempts); + mPartitionIdChanges = HostSwap16(aMleCounters.mPartitionIdChanges); + mBetterPartitionAttachAttempts = HostSwap16(aMleCounters.mBetterPartitionAttachAttempts); + mParentChanges = HostSwap16(aMleCounters.mParentChanges); + mTrackedTime = HostSwap64(aMleCounters.mTrackedTime); + mDisabledTime = HostSwap64(aMleCounters.mDisabledTime); + mDetachedTime = HostSwap64(aMleCounters.mDetachedTime); + mChildTime = HostSwap64(aMleCounters.mChildTime); + mRouterTime = HostSwap64(aMleCounters.mRouterTime); + mLeaderTime = HostSwap64(aMleCounters.mLeaderTime); +} + +void MleCountersTlv::Read(MleCounters &aDiagMleCounters) const +{ + aDiagMleCounters.mDisabledRole = HostSwap16(mDisabledRole); + aDiagMleCounters.mDetachedRole = HostSwap16(mDetachedRole); + aDiagMleCounters.mChildRole = HostSwap16(mChildRole); + aDiagMleCounters.mRouterRole = HostSwap16(mRouterRole); + aDiagMleCounters.mLeaderRole = HostSwap16(mLeaderRole); + aDiagMleCounters.mAttachAttempts = HostSwap16(mAttachAttempts); + aDiagMleCounters.mPartitionIdChanges = HostSwap16(mPartitionIdChanges); + aDiagMleCounters.mBetterPartitionAttachAttempts = HostSwap16(mBetterPartitionAttachAttempts); + aDiagMleCounters.mParentChanges = HostSwap16(mParentChanges); + aDiagMleCounters.mTrackedTime = HostSwap64(mTrackedTime); + aDiagMleCounters.mDisabledTime = HostSwap64(mDisabledTime); + aDiagMleCounters.mDetachedTime = HostSwap64(mDetachedTime); + aDiagMleCounters.mChildTime = HostSwap64(mChildTime); + aDiagMleCounters.mRouterTime = HostSwap64(mRouterTime); + aDiagMleCounters.mLeaderTime = HostSwap64(mLeaderTime); +} + +} // namespace NetworkDiagnostic +} // namespace ot diff --git a/src/core/thread/network_diagnostic_tlvs.hpp b/src/core/thread/network_diagnostic_tlvs.hpp index cc366c56a..8a98fd525 100644 --- a/src/core/thread/network_diagnostic_tlvs.hpp +++ b/src/core/thread/network_diagnostic_tlvs.hpp @@ -28,7 +28,7 @@ /** * @file - * This file includes definitions for generating and processing MLE TLVs. + * This file includes definitions for generating and processing Network Diagnostics TLVs. */ #ifndef NETWORK_DIAGNOSTIC_TLVS_HPP_ @@ -48,6 +48,7 @@ #include "thread/link_quality.hpp" #include "thread/mle_tlvs.hpp" #include "thread/mle_types.hpp" +#include "thread/topology.hpp" namespace ot { namespace NetworkDiagnostic { @@ -69,27 +70,33 @@ public: */ enum Type : uint8_t { - kExtMacAddress = OT_NETWORK_DIAGNOSTIC_TLV_EXT_ADDRESS, - kAddress16 = OT_NETWORK_DIAGNOSTIC_TLV_SHORT_ADDRESS, - kMode = OT_NETWORK_DIAGNOSTIC_TLV_MODE, - kTimeout = OT_NETWORK_DIAGNOSTIC_TLV_TIMEOUT, - kConnectivity = OT_NETWORK_DIAGNOSTIC_TLV_CONNECTIVITY, - kRoute = OT_NETWORK_DIAGNOSTIC_TLV_ROUTE, - kLeaderData = OT_NETWORK_DIAGNOSTIC_TLV_LEADER_DATA, - kNetworkData = OT_NETWORK_DIAGNOSTIC_TLV_NETWORK_DATA, - kIp6AddressList = OT_NETWORK_DIAGNOSTIC_TLV_IP6_ADDR_LIST, - kMacCounters = OT_NETWORK_DIAGNOSTIC_TLV_MAC_COUNTERS, - kBatteryLevel = OT_NETWORK_DIAGNOSTIC_TLV_BATTERY_LEVEL, - kSupplyVoltage = OT_NETWORK_DIAGNOSTIC_TLV_SUPPLY_VOLTAGE, - kChildTable = OT_NETWORK_DIAGNOSTIC_TLV_CHILD_TABLE, - kChannelPages = OT_NETWORK_DIAGNOSTIC_TLV_CHANNEL_PAGES, - kTypeList = OT_NETWORK_DIAGNOSTIC_TLV_TYPE_LIST, - kMaxChildTimeout = OT_NETWORK_DIAGNOSTIC_TLV_MAX_CHILD_TIMEOUT, - kVersion = OT_NETWORK_DIAGNOSTIC_TLV_VERSION, - kVendorName = OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_NAME, - kVendorModel = OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_MODEL, - kVendorSwVersion = OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_SW_VERSION, - kThreadStackVersion = OT_NETWORK_DIAGNOSTIC_TLV_THREAD_STACK_VERSION, + kExtMacAddress = OT_NETWORK_DIAGNOSTIC_TLV_EXT_ADDRESS, + kAddress16 = OT_NETWORK_DIAGNOSTIC_TLV_SHORT_ADDRESS, + kMode = OT_NETWORK_DIAGNOSTIC_TLV_MODE, + kTimeout = OT_NETWORK_DIAGNOSTIC_TLV_TIMEOUT, + kConnectivity = OT_NETWORK_DIAGNOSTIC_TLV_CONNECTIVITY, + kRoute = OT_NETWORK_DIAGNOSTIC_TLV_ROUTE, + kLeaderData = OT_NETWORK_DIAGNOSTIC_TLV_LEADER_DATA, + kNetworkData = OT_NETWORK_DIAGNOSTIC_TLV_NETWORK_DATA, + kIp6AddressList = OT_NETWORK_DIAGNOSTIC_TLV_IP6_ADDR_LIST, + kMacCounters = OT_NETWORK_DIAGNOSTIC_TLV_MAC_COUNTERS, + kBatteryLevel = OT_NETWORK_DIAGNOSTIC_TLV_BATTERY_LEVEL, + kSupplyVoltage = OT_NETWORK_DIAGNOSTIC_TLV_SUPPLY_VOLTAGE, + kChildTable = OT_NETWORK_DIAGNOSTIC_TLV_CHILD_TABLE, + kChannelPages = OT_NETWORK_DIAGNOSTIC_TLV_CHANNEL_PAGES, + kTypeList = OT_NETWORK_DIAGNOSTIC_TLV_TYPE_LIST, + kMaxChildTimeout = OT_NETWORK_DIAGNOSTIC_TLV_MAX_CHILD_TIMEOUT, + kVersion = OT_NETWORK_DIAGNOSTIC_TLV_VERSION, + kVendorName = OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_NAME, + kVendorModel = OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_MODEL, + kVendorSwVersion = OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_SW_VERSION, + kThreadStackVersion = OT_NETWORK_DIAGNOSTIC_TLV_THREAD_STACK_VERSION, + kChild = OT_NETWORK_DIAGNOSTIC_TLV_CHILD, + kChildIp6AddressList = OT_NETWORK_DIAGNOSTIC_TLV_CHILD_IP6_ADDR_LIST, + kRouterNeighbor = OT_NETWORK_DIAGNOSTIC_TLV_ROUTER_NEIGHBOR, + kAnswer = OT_NETWORK_DIAGNOSTIC_TLV_ANSWER, + kQueryId = OT_NETWORK_DIAGNOSTIC_TLV_QUERY_ID, + kMleCounters = OT_NETWORK_DIAGNOSTIC_TLV_MLE_COUNTERS, }; /** @@ -224,6 +231,18 @@ typedef StringTlvInfo Ven */ typedef StringTlvInfo ThreadStackVersionTlv; +/** + * Defines Child IPv6 Address List TLV constants and types. + * + */ +typedef TlvInfo ChildIp6AddressListTlv; + +/** + * Defines Query ID TLV constants and types. + * + */ +typedef UintTlvInfo QueryIdTlv; + typedef otNetworkDiagConnectivity Connectivity; ///< Network Diagnostic Connectivity value. /** @@ -667,6 +686,465 @@ public: } } OT_TOOL_PACKED_END; +#if OPENTHREAD_FTD + +/** + * Implements Child TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class ChildTlv : public Tlv, public TlvInfo, public Clearable +{ +public: + static constexpr uint8_t kFlagsRxOnWhenIdle = 1 << 7; ///< Device mode - Rx-on when idle. + static constexpr uint8_t kFlagsFtd = 1 << 6; ///< Device mode - Full Thread Device (FTD). + static constexpr uint8_t kFlagsFullNetdta = 1 << 5; ///< Device mode - Full Network Data. + static constexpr uint8_t kFlagsCslSync = 1 << 4; ///< Is CSL capable and CSL synchronized. + static constexpr uint8_t kFlagsTrackErrRate = 1 << 3; ///< Supports tracking error rates. + + /** + * Initializes the TLV using information from a given `Child`. + * + * @param[in] aChild The child to initialize the TLV from. + * + */ + void InitFrom(const Child &aChild); + + /** + * Initializes the TLV as empty (zero length). + * + */ + void InitAsEmpty(void) + { + SetType(kChild); + SetLength(0); + } + + /** + * Returns the Flags field (`kFlags*` constants define bits in flags). + * + * @returns The Flags field. + * + */ + uint8_t GetFlags(void) const { return mFlags; } + + /** + * Returns the RLOC16 field. + * + * @returns The RLOC16 of the child. + * + */ + uint16_t GetRloc16(void) const { return HostSwap16(mRloc16); } + + /** + * Returns the Extended Address. + * + * @returns The Extended Address of the child. + * + */ + const Mac::ExtAddress &GetExtAddress(void) const { return mExtAddress; } + + /** + * Returns the Version field. + * + * @returns The Version of the child. + * + */ + uint16_t GetVersion(void) const { return HostSwap16(mVersion); } + + /** + * Returns the Timeout field + * + * @returns The Timeout value in seconds. + * + */ + uint32_t GetTimeout(void) const { return HostSwap32(mTimeout); } + + /** + * Returns the Age field. + * + * @returns The Age field (seconds since last heard from the child). + * + */ + uint32_t GetAge(void) const { return HostSwap32(mAge); } + + /** + * Returns the Connection Time field. + * + * @returns The Connection Time field (seconds since attach). + * + */ + uint32_t GetConnectionTime(void) const { return HostSwap32(mConnectionTime); } + + /** + * Returns the Supervision Interval field + * + * @returns The Supervision Interval in seconds. Zero indicates not used. + * + */ + uint16_t GetSupervisionInterval(void) const { return HostSwap16(mSupervisionInterval); } + + /** + * Returns the Link Margin field. + * + * @returns The Link Margin in dB. + * + */ + uint8_t GetLinkMargin(void) const { return mLinkMargin; } + + /** + * Returns the Average RSSI field. + * + * @returns The Average RSSI in dBm. 127 if not available or unknown. + * + */ + int8_t GetAverageRssi(void) const { return mAverageRssi; } + + /** + * Returns the Last RSSI field (RSSI of last received frame from child). + * + * @returns The Last RSSI field in dBm. 127 if not available or unknown. + * + */ + int8_t GetLastRssi(void) const { return mLastRssi; } + + /** + * Returns the Frame Error Rate field. + * + * `kFlagsTrackErrRate` from `GetFlags()` indicates whether or not the implementation supports tracking of error + * rates and whether or not the value in this field is valid. + * + * @returns The Frame Error Rate (0x0000->0%, 0xffff->100%). + * + */ + uint16_t GetFrameErrorRate(void) const { return HostSwap16(mFrameErrorRate); } + + /** + * Returns the Message Error Rate field. + * + * `kFlagsTrackErrRate` from `GetFlags()` indicates whether or not the implementation supports tracking of error + * rates and whether or not the value in this field is valid. + * + * @returns The Message Error Rate (0x0000->0%, 0xffff->100%). + * + */ + uint16_t GetMessageErrorRate(void) const { return HostSwap16(mMessageErrorRate); } + + /** + * Returns the Queued Message Count field. + * + * @returns The Queued Message Count (number of queued messages for indirect tx to child). + * + */ + uint16_t GetQueuedMessageCount(void) const { return HostSwap16(mQueuedMessageCount); } + + /** + * Returns the CSL Period in unit of 10 symbols. + * + * @returns The CSL Period in unit of 10-symbols-time. Zero if CSL is not supported. + * + */ + uint16_t GetCslPeriod(void) const { return HostSwap16(mCslPeriod); } + + /** + * Returns the CSL Timeout in seconds. + * + * @returns The CSL Timeout in seconds. Zero if unknown on parent of if CSL Is not supported. + * + */ + uint32_t GetCslTimeout(void) const { return HostSwap32(mCslTimeout); } + + /** + * Returns the CSL Channel. + * + * @returns The CSL channel. + * + */ + uint8_t GetCslChannel(void) const { return mCslChannel; } + +private: + uint8_t mFlags; // Flags (`kFlags*` constants). + uint16_t mRloc16; // RLOC16. + Mac::ExtAddress mExtAddress; // Extended Address. + uint16_t mVersion; // Version. + uint32_t mTimeout; // Timeout in seconds. + uint32_t mAge; // Seconds since last heard from the child. + uint32_t mConnectionTime; // Seconds since attach. + uint16_t mSupervisionInterval; // Supervision interval in seconds. Zero to indicate not used. + uint8_t mLinkMargin; // Link margin in dB. + int8_t mAverageRssi; // Average RSSI. 127 if not available or unknown + int8_t mLastRssi; // RSSI of last received frame. 127 if not available or unknown. + uint16_t mFrameErrorRate; // Frame error rate (0x0000->0%, 0xffff->100%). + uint16_t mMessageErrorRate; // (IPv6) msg error rate (0x0000->0%, 0xffff->100%) + uint16_t mQueuedMessageCount; // Number of queued messages for indirect tx to child. + uint16_t mCslPeriod; // CSL Period in unit of 10 symbols. + uint32_t mCslTimeout; // CSL Timeout in seconds. + uint8_t mCslChannel; // CSL channel. +} OT_TOOL_PACKED_END; + +/** + * Implements Child IPv6 Address List Value generation and parsing. + * + * This TLV can use extended or normal format depending on the number of IPv6 addresses. + * + */ +OT_TOOL_PACKED_BEGIN +class ChildIp6AddressListTlvValue +{ +public: + /** + * Returns the RLOC16 of the child. + * + * @returns The RLOC16 of the child. + * + */ + uint16_t GetRloc16(void) const { return HostSwap16(mRloc16); } + + /** + * Sets the RLOC16. + * + * @param[in] aRloc16 The RLOC16 value. + * + */ + void SetRloc16(uint16_t aRloc16) { mRloc16 = HostSwap16(aRloc16); } + +private: + uint16_t mRloc16; + // Followed by zero or more IPv6 address(es). + +} OT_TOOL_PACKED_END; + +/** + * Implements Router Neighbor TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class RouterNeighborTlv : public Tlv, public TlvInfo, public Clearable +{ +public: + static constexpr uint8_t kFlagsTrackErrRate = 1 << 7; ///< Supports tracking error rates. + + /** + * Initializes the TLV using information from a given `Router`. + * + * @param[in] aRouter The router to initialize the TLV from. + * + */ + void InitFrom(const Router &aRouter); + + /** + * Initializes the TLV as empty (zero length). + * + */ + void InitAsEmpty(void) + { + SetType(kRouterNeighbor); + SetLength(0); + } + + /** + * Returns the Flags field (`kFlags*` constants define bits in flags). + * + * @returns The Flags field. + * + */ + uint8_t GetFlags(void) const { return mFlags; } + + /** + * Returns the RLOC16 field. + * + * @returns The RLOC16 of the router. + * + */ + uint16_t GetRloc16(void) const { return HostSwap16(mRloc16); } + + /** + * Returns the Extended Address. + * + * @returns The Extended Address of the router. + * + */ + const Mac::ExtAddress &GetExtAddress(void) const { return mExtAddress; } + + /** + * Returns the Version field. + * + * @returns The Version of the router. + * + */ + uint16_t GetVersion(void) const { return HostSwap16(mVersion); } + + /** + * Returns the Connection Time field. + * + * @returns The Connection Time field (seconds since link establishment). + * + */ + uint32_t GetConnectionTime(void) const { return HostSwap32(mConnectionTime); } + + /** + * Returns the Link Margin field. + * + * @returns The Link Margin in dB. + * + */ + uint8_t GetLinkMargin(void) const { return mLinkMargin; } + + /** + * Returns the Average RSSI field. + * + * @returns The Average RSSI in dBm. 127 if not available or unknown. + * + */ + int8_t GetAverageRssi(void) const { return mAverageRssi; } + + /** + * Returns the Last RSSI field (RSSI of last received frame from router). + * + * @returns The Last RSSI field in dBm. 127 if not available or unknown. + * + */ + int8_t GetLastRssi(void) const { return mLastRssi; } + + /** + * Returns the Frame Error Rate field. + * + * `kFlagsTrackErrRate` from `GetFlags()` indicates whether or not the implementation supports tracking of error + * rates and whether or not the value in this field is valid. + * + * @returns The Frame Error Rate (0x0000->0%, 0xffff->100%). + * + */ + uint16_t GetFrameErrorRate(void) const { return HostSwap16(mFrameErrorRate); } + + /** + * Returns the Message Error Rate field. + * + * `kFlagsTrackErrRate` from `GetFlags()` indicates whether or not the implementation supports tracking of error + * rates and whether or not the value in this field is valid. + * + * @returns The Message Error Rate (0x0000->0%, 0xffff->100%). + * + */ + uint16_t GetMessageErrorRate(void) const { return HostSwap16(mMessageErrorRate); } + +private: + uint8_t mFlags; // Flags (`kFlags*` constants). + uint16_t mRloc16; // RLOC16. + Mac::ExtAddress mExtAddress; // Extended Address. + uint16_t mVersion; // Version. + uint32_t mConnectionTime; // Seconds since link establishment. + uint8_t mLinkMargin; // Link Margin. + int8_t mAverageRssi; // Average RSSI. 127 if not available or unknown + int8_t mLastRssi; // RSSI of last received frame. 127 if not available or unknown. + uint16_t mFrameErrorRate; // Frame error rate (0x0000->0%, 0xffff->100%). + uint16_t mMessageErrorRate; // (IPv6) msg error rate (0x0000->0%, 0xffff->100%) +} OT_TOOL_PACKED_END; + +#endif // OPENTHREAD_FTD + +/** + * Implements Answer TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class AnswerTlv : public Tlv, public TlvInfo +{ +public: + /** + * Initializes the TLV. + * + * @param[in] aIndex The index value. + * @param[in] aIsLast The "IsLast" flag value. + * + */ + void Init(uint16_t aIndex, bool aIsLast); + + /** + * Indicates whether or not the "IsLast" flag is set + * + * @retval TRUE "IsLast" flag si set (this is the last answer for this query). + * @retval FALSE "IsLast" flag is not set (more answer messages are expected for this query). + * + */ + bool IsLast(void) const { return GetFlagsIndex() & kIsLastFlag; } + + /** + * Gets the index. + * + * @returns The index. + * + */ + uint16_t GetIndex(void) const { return GetFlagsIndex() & kIndexMask; } + +private: + static constexpr uint16_t kIsLastFlag = 1 << 15; + static constexpr uint16_t kIndexMask = 0x7f; + + uint16_t GetFlagsIndex(void) const { return HostSwap16(mFlagsIndex); } + void SetFlagsIndex(uint16_t aFlagsIndex) { mFlagsIndex = HostSwap16(aFlagsIndex); } + + uint16_t mFlagsIndex; +} OT_TOOL_PACKED_END; + +/** + * Represents the MLE Counters. + * + */ +typedef otNetworkDiagMleCounters MleCounters; + +/** + * Implements MLE Counters TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class MleCountersTlv : public Tlv, public TlvInfo +{ +public: + /** + * Initializes the TLV. + * + * @param[in] aMleCounter The MLE counters to initialize the TLV with. + * + */ + void Init(const Mle::Counters &aMleCounters); + + /** + * Indicates whether or not the TLV appears to be well-formed. + * + * @retval TRUE If the TLV appears to be well-formed. + * @retval FALSE If the TLV does not appear to be well-formed. + * + */ + bool IsValid(void) const { return GetLength() >= sizeof(*this) - sizeof(Tlv); } + + /** + * + * Reads the counters from TLV. + * + * @param[out] aDiagMleCounters A reference to `NetworkDiagnostic::MleCounters` to populate. + * + */ + void Read(MleCounters &aDiagMleCounters) const; + +private: + uint16_t mDisabledRole; // Number of times device entered disabled role. + uint16_t mDetachedRole; // Number of times device entered detached role. + uint16_t mChildRole; // Number of times device entered child role. + uint16_t mRouterRole; // Number of times device entered router role. + uint16_t mLeaderRole; // Number of times device entered leader role. + uint16_t mAttachAttempts; // Number of attach attempts while device was detached. + uint16_t mPartitionIdChanges; // Number of changes to partition ID. + uint16_t mBetterPartitionAttachAttempts; // Number of attempts to attach to a better partition. + uint16_t mParentChanges; // Number of time device changed its parent. + uint64_t mTrackedTime; // Milliseconds tracked by next counters. + uint64_t mDisabledTime; // Milliseconds device has been in disabled role. + uint64_t mDetachedTime; // Milliseconds device has been in detached role. + uint64_t mChildTime; // Milliseconds device has been in child role. + uint64_t mRouterTime; // Milliseconds device has been in router role. + uint64_t mLeaderTime; // Milliseconds device has been in leader role. +} OT_TOOL_PACKED_END; + } // namespace NetworkDiagnostic } // namespace ot diff --git a/src/core/utils/mesh_diag.cpp b/src/core/utils/mesh_diag.cpp index 1e08e855f..60d78f15c 100644 --- a/src/core/utils/mesh_diag.cpp +++ b/src/core/utils/mesh_diag.cpp @@ -45,7 +45,7 @@ namespace ot { namespace Utils { -using namespace ot::NetworkDiagnostic; +using namespace NetworkDiagnostic; RegisterLogModule("MeshDiag"); @@ -54,54 +54,23 @@ RegisterLogModule("MeshDiag"); MeshDiag::MeshDiag(Instance &aInstance) : InstanceLocator(aInstance) + , mState(kStateIdle) + , mExpectedQueryId(0) + , mExpectedAnswerIndex(0) , mTimer(aInstance) { } Error MeshDiag::DiscoverTopology(const DiscoverConfig &aConfig, DiscoverCallback aCallback, void *aContext) -{ - Error error = kErrorNone; - - VerifyOrExit(Get().IsAttached(), error = kErrorInvalidState); - VerifyOrExit(!mTimer.IsRunning(), error = kErrorBusy); - - Get().GetRouterIdSet(mExpectedRouterIdSet); - - for (uint8_t routerId = 0; routerId <= Mle::kMaxRouterId; routerId++) - { - if (mExpectedRouterIdSet.Contains(routerId)) - { - SuccessOrExit(error = SendDiagGetTo(Mle::Rloc16FromRouterId(routerId), aConfig)); - } - } - - mDiscoverCallback.Set(aCallback, aContext); - mTimer.Start(kResponseTimeout); - -exit: - return error; -} - -void MeshDiag::Cancel(void) -{ - mTimer.Stop(); - IgnoreError(Get().AbortTransaction(HandleDiagGetResponse, this)); -} - -Error MeshDiag::SendDiagGetTo(uint16_t aRloc16, const DiscoverConfig &aConfig) { static constexpr uint8_t kMaxTlvsToRequest = 6; - Error error = kErrorNone; - Coap::Message *message = nullptr; - Tmf::MessageInfo messageInfo(GetInstance()); - uint8_t tlvs[kMaxTlvsToRequest]; - uint8_t tlvsLength = 0; + Error error = kErrorNone; + uint8_t tlvs[kMaxTlvsToRequest]; + uint8_t tlvsLength = 0; - message = Get().NewConfirmablePostMessage(kUriDiagnosticGetRequest); - VerifyOrExit(message != nullptr, error = kErrorNoBufs); - - IgnoreError(message->SetPriority(Message::kPriorityLow)); + VerifyOrExit(Get().IsAttached(), error = kErrorInvalidState); + VerifyOrExit(mState == kStateIdle, error = kErrorBusy); tlvs[tlvsLength++] = Address16Tlv::kType; tlvs[tlvsLength++] = ExtMacAddressTlv::kType; @@ -118,13 +87,29 @@ Error MeshDiag::SendDiagGetTo(uint16_t aRloc16, const DiscoverConfig &aConfig) tlvs[tlvsLength++] = ChildTableTlv::kType; } - SuccessOrExit(error = Tlv::Append(*message, tlvs, tlvsLength)); + Get().GetRouterIdSet(mDiscover.mExpectedRouterIdSet); - messageInfo.SetSockAddrToRlocPeerAddrTo(aRloc16); - error = Get().SendMessage(*message, messageInfo, HandleDiagGetResponse, this); + for (uint8_t routerId = 0; routerId <= Mle::kMaxRouterId; routerId++) + { + Ip6::Address destination; + + if (!mDiscover.mExpectedRouterIdSet.Contains(routerId)) + { + continue; + } + + destination = Get().GetMeshLocal16(); + destination.GetIid().SetLocator(Mle::Rloc16FromRouterId(routerId)); + + SuccessOrExit(error = Get().SendCommand(kUriDiagnosticGetRequest, Message::kPriorityLow, destination, + tlvs, tlvsLength, HandleDiagGetResponse, this)); + } + + mDiscover.mCallback.Set(aCallback, aContext); + mState = kStateDicoverTopology; + mTimer.Start(kResponseTimeout); exit: - FreeMessageOnError(message, error); return error; } @@ -147,7 +132,8 @@ void MeshDiag::HandleDiagGetResponse(Coap::Message *aMessage, const Ip6::Message ChildIterator childIterator; SuccessOrExit(aResult); - VerifyOrExit((aMessage != nullptr) && mTimer.IsRunning()); + VerifyOrExit(aMessage != nullptr); + VerifyOrExit(mState == kStateDicoverTopology); SuccessOrExit(routerInfo.ParseFrom(*aMessage)); @@ -161,11 +147,12 @@ void MeshDiag::HandleDiagGetResponse(Coap::Message *aMessage, const Ip6::Message routerInfo.mChildIterator = &childIterator; } - mExpectedRouterIdSet.Remove(routerInfo.mRouterId); + mDiscover.mExpectedRouterIdSet.Remove(routerInfo.mRouterId); - if (mExpectedRouterIdSet.GetNumberOfAllocatedIds() == 0) + if (mDiscover.mExpectedRouterIdSet.GetNumberOfAllocatedIds() == 0) { - error = kErrorNone; + error = kErrorNone; + mState = kStateIdle; mTimer.Stop(); } else @@ -173,21 +160,326 @@ void MeshDiag::HandleDiagGetResponse(Coap::Message *aMessage, const Ip6::Message error = kErrorPending; } - mDiscoverCallback.InvokeIfSet(error, &routerInfo); + mDiscover.mCallback.InvokeIfSet(error, &routerInfo); exit: return; } -void MeshDiag::HandleTimer(void) +Error MeshDiag::SendQuery(uint16_t aRloc16, const uint8_t *aTlvs, uint8_t aTlvsLength) { - // Timed out waiting for response from one or more routers. + Error error = kErrorNone; + Ip6::Address destination; - IgnoreError(Get().AbortTransaction(HandleDiagGetResponse, this)); + VerifyOrExit(Get().IsAttached(), error = kErrorInvalidState); + VerifyOrExit(mState == kStateIdle, error = kErrorBusy); + VerifyOrExit(Mle::IsActiveRouter(aRloc16), error = kErrorInvalidArgs); + VerifyOrExit(Get().IsAllocated(Mle::RouterIdFromRloc16(aRloc16)), error = kErrorNotFound); - mDiscoverCallback.InvokeIfSet(kErrorResponseTimeout, nullptr); + destination = Get().GetMeshLocal16(); + destination.GetIid().SetLocator(aRloc16); + + SuccessOrExit(error = Get().SendCommand(kUriDiagnosticGetQuery, Message::kPriorityNormal, destination, + aTlvs, aTlvsLength)); + + mExpectedQueryId = Get().GetLastQueryId(); + mExpectedAnswerIndex = 0; + + mTimer.Start(kResponseTimeout); + +exit: + return error; } +Error MeshDiag::QueryChildTable(uint16_t aRloc16, QueryChildTableCallback aCallback, void *aContext) +{ + static const uint8_t kTlvTypes[] = {ChildTlv::kType}; + + Error error; + + SuccessOrExit(error = SendQuery(aRloc16, kTlvTypes, sizeof(kTlvTypes))); + + mQueryChildTable.mCallback.Set(aCallback, aContext); + mQueryChildTable.mRouterRloc16 = aRloc16; + mState = kStateQueryChildTable; + +exit: + return error; +} + +Error MeshDiag::QueryChildrenIp6Addrs(uint16_t aRloc16, ChildIp6AddrsCallback aCallback, void *aContext) +{ + static const uint8_t kTlvTypes[] = {ChildIp6AddressListTlv::kType}; + + Error error; + + SuccessOrExit(error = SendQuery(aRloc16, kTlvTypes, sizeof(kTlvTypes))); + + mQueryChildrenIp6Addrs.mCallback.Set(aCallback, aContext); + mQueryChildrenIp6Addrs.mParentRloc16 = aRloc16; + mState = kStateQueryChildrenIp6Addrs; + +exit: + return error; +} + +Error MeshDiag::QueryRouterNeighborTable(uint16_t aRloc16, RouterNeighborTableCallback aCallback, void *aContext) +{ + static const uint8_t kTlvTypes[] = {RouterNeighborTlv::kType}; + + Error error; + + SuccessOrExit(error = SendQuery(aRloc16, kTlvTypes, sizeof(kTlvTypes))); + + mQueryRouterNeighborTable.mCallback.Set(aCallback, aContext); + mQueryRouterNeighborTable.mRouterRloc16 = aRloc16; + mState = kStateQueryRouterNeighborTable; + +exit: + return error; +} + +bool MeshDiag::HandleDiagnosticGetAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + bool didPorcess = false; + + switch (mState) + { + case kStateQueryChildTable: + didPorcess = ProcessChildTableAnswer(aMessage, aMessageInfo); + break; + + case kStateQueryChildrenIp6Addrs: + didPorcess = ProcessChildrenIp6AddrsAnswer(aMessage, aMessageInfo); + break; + + case kStateQueryRouterNeighborTable: + didPorcess = ProcessRouterNeighborTableAnswer(aMessage, aMessageInfo); + break; + + default: + break; + } + + return didPorcess; +} + +Error MeshDiag::ProcessMessage(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint16_t aSenderRloc16) +{ + // This method processes the received answer message to + // check whether it is from the intended sender and matches + // the expected query ID and answer index. + + Error error = kErrorFailed; + AnswerTlv answerTlv; + uint16_t queryId; + + VerifyOrExit(Get().IsRoutingLocator(aMessageInfo.GetPeerAddr())); + VerifyOrExit(aMessageInfo.GetPeerAddr().GetIid().GetLocator() == aSenderRloc16); + + SuccessOrExit(Tlv::Find(aMessage, queryId)); + VerifyOrExit(queryId == mExpectedQueryId); + + SuccessOrExit(Tlv::FindTlv(aMessage, answerTlv)); + + if (answerTlv.GetIndex() != mExpectedAnswerIndex) + { + Finalize(kErrorResponseTimeout); + ExitNow(); + } + + mExpectedAnswerIndex++; + error = kErrorNone; + +exit: + return error; +} + +bool MeshDiag::ProcessChildTableAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + bool didPorcess = false; + ChildTlv childTlv; + ChildEntry entry; + uint16_t offset; + + SuccessOrExit(ProcessMessage(aMessage, aMessageInfo, mQueryChildTable.mRouterRloc16)); + + while (true) + { + SuccessOrExit(Tlv::FindTlv(aMessage, childTlv, offset)); + VerifyOrExit(!childTlv.IsExtended()); + + didPorcess = true; + + if (childTlv.GetLength() == 0) + { + // We reached end of the list. + mState = kStateIdle; + mTimer.Stop(); + mQueryChildTable.mCallback.InvokeIfSet(kErrorNone, nullptr); + ExitNow(); + } + + VerifyOrExit(childTlv.GetLength() >= sizeof(ChildTlv) - sizeof(Tlv)); + IgnoreError(aMessage.Read(offset, childTlv)); + + entry.SetFrom(childTlv); + mQueryChildTable.mCallback.InvokeIfSet(kErrorPending, &entry); + + // Make sure query operation is not canceled from the + // callback. + VerifyOrExit(mState == kStateQueryChildTable); + + aMessage.SetOffset(static_cast(offset + childTlv.GetSize())); + } + +exit: + return didPorcess; +} + +bool MeshDiag::ProcessRouterNeighborTableAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + bool didPorcess = false; + RouterNeighborTlv neighborTlv; + RouterNeighborEntry entry; + uint16_t offset; + + SuccessOrExit(ProcessMessage(aMessage, aMessageInfo, mQueryRouterNeighborTable.mRouterRloc16)); + + while (true) + { + SuccessOrExit(Tlv::FindTlv(aMessage, neighborTlv, offset)); + VerifyOrExit(!neighborTlv.IsExtended()); + + didPorcess = true; + + if (neighborTlv.GetLength() == 0) + { + // We reached end of the list. + mState = kStateIdle; + mTimer.Stop(); + mQueryRouterNeighborTable.mCallback.InvokeIfSet(kErrorNone, nullptr); + ExitNow(); + } + + VerifyOrExit(neighborTlv.GetLength() >= sizeof(RouterNeighborTlv) - sizeof(Tlv)); + + entry.SetFrom(neighborTlv); + mQueryRouterNeighborTable.mCallback.InvokeIfSet(kErrorPending, &entry); + + // Make sure query operation is not canceled from the + // callback. + VerifyOrExit(mState == kStateQueryRouterNeighborTable); + + aMessage.SetOffset(static_cast(offset + neighborTlv.GetSize())); + } + +exit: + return didPorcess; +} + +bool MeshDiag::ProcessChildrenIp6AddrsAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo) +{ + bool didPorcess = false; + uint16_t offset; + uint16_t endOffset; + ChildIp6AddressListTlvValue tlvValue; + Ip6AddrIterator ip6AddrIterator; + + SuccessOrExit(ProcessMessage(aMessage, aMessageInfo, mQueryChildrenIp6Addrs.mParentRloc16)); + + while (true) + { + SuccessOrExit(Tlv::FindTlvValueStartEndOffsets(aMessage, ChildIp6AddressListTlv::kType, offset, endOffset)); + + didPorcess = true; + + if (offset == endOffset) + { + // We reached end of the list + mState = kStateIdle; + mTimer.Stop(); + mQueryChildrenIp6Addrs.mCallback.InvokeIfSet(kErrorNone, Mle::kInvalidRloc16, nullptr); + ExitNow(); + } + + // Read the `ChildIp6AddressListTlvValue` (which contains the + // child RLOC16) and then prepare the `Ip6AddrIterator`. + + VerifyOrExit(offset + sizeof(tlvValue) <= endOffset); + IgnoreError(aMessage.Read(offset, tlvValue)); + offset += sizeof(tlvValue); + + ip6AddrIterator.mMessage = &aMessage; + ip6AddrIterator.mCurOffset = offset; + ip6AddrIterator.mEndOffset = endOffset; + + mQueryChildrenIp6Addrs.mCallback.InvokeIfSet(kErrorPending, tlvValue.GetRloc16(), &ip6AddrIterator); + + // Make sure query operation is not canceled from the + // callback. + VerifyOrExit(mState == kStateQueryChildrenIp6Addrs); + + aMessage.SetOffset(endOffset); + } + +exit: + return didPorcess; +} + +void MeshDiag::Cancel(void) +{ + switch (mState) + { + case kStateIdle: + case kStateQueryChildTable: + case kStateQueryChildrenIp6Addrs: + case kStateQueryRouterNeighborTable: + break; + + case kStateDicoverTopology: + IgnoreError(Get().AbortTransaction(HandleDiagGetResponse, this)); + break; + } + + mState = kStateIdle; + mTimer.Stop(); +} + +void MeshDiag::Finalize(Error aError) +{ + // Finalize an ongoing query operation (if any) invoking + // the corresponding callback with `aError`. + + State oldState = mState; + + Cancel(); + + switch (oldState) + { + case kStateIdle: + break; + + case kStateDicoverTopology: + mDiscover.mCallback.InvokeIfSet(aError, nullptr); + break; + + case kStateQueryChildTable: + mQueryChildTable.mCallback.InvokeIfSet(aError, nullptr); + break; + + case kStateQueryChildrenIp6Addrs: + mQueryChildrenIp6Addrs.mCallback.InvokeIfSet(aError, Mle::kInvalidRloc16, nullptr); + break; + + case kStateQueryRouterNeighborTable: + mQueryRouterNeighborTable.mCallback.InvokeIfSet(aError, nullptr); + break; + } +} + +void MeshDiag::HandleTimer(void) { Finalize(kErrorResponseTimeout); } + //--------------------------------------------------------------------------------------------------------------------- // MeshDiag::RouterInfo @@ -299,6 +591,51 @@ exit: return error; } +//--------------------------------------------------------------------------------------------------------------------- +// MeshDiag::ChildEntry + +void MeshDiag::ChildEntry::SetFrom(const ChildTlv &aChildTlv) +{ + mRxOnWhenIdle = (aChildTlv.GetFlags() & ChildTlv::kFlagsRxOnWhenIdle); + mDeviceTypeFtd = (aChildTlv.GetFlags() & ChildTlv::kFlagsFtd); + mFullNetData = (aChildTlv.GetFlags() & ChildTlv::kFlagsFullNetdta); + mCslSynchronized = (aChildTlv.GetFlags() & ChildTlv::kFlagsCslSync); + mSupportsErrRate = (aChildTlv.GetFlags() & ChildTlv::kFlagsTrackErrRate); + mRloc16 = aChildTlv.GetRloc16(); + mExtAddress = aChildTlv.GetExtAddress(); + mVersion = aChildTlv.GetVersion(); + mTimeout = aChildTlv.GetTimeout(); + mAge = aChildTlv.GetAge(); + mConnectionTime = aChildTlv.GetConnectionTime(); + mSupervisionInterval = aChildTlv.GetSupervisionInterval(); + mLinkMargin = aChildTlv.GetLinkMargin(); + mAverageRssi = aChildTlv.GetAverageRssi(); + mLastRssi = aChildTlv.GetLastRssi(); + mFrameErrorRate = aChildTlv.GetFrameErrorRate(); + mMessageErrorRate = aChildTlv.GetMessageErrorRate(); + mQueuedMessageCount = aChildTlv.GetQueuedMessageCount(); + mCslPeriod = aChildTlv.GetCslPeriod(); + mCslTimeout = aChildTlv.GetCslTimeout(); + mCslChannel = aChildTlv.GetCslChannel(); +} + +//--------------------------------------------------------------------------------------------------------------------- +// MeshDiag::RouterNeighborEntry + +void MeshDiag::RouterNeighborEntry::SetFrom(const RouterNeighborTlv &aTlv) +{ + mSupportsErrRate = (aTlv.GetFlags() & RouterNeighborTlv::kFlagsTrackErrRate); + mRloc16 = aTlv.GetRloc16(); + mExtAddress = aTlv.GetExtAddress(); + mVersion = aTlv.GetVersion(); + mConnectionTime = aTlv.GetConnectionTime(); + mLinkMargin = aTlv.GetLinkMargin(); + mAverageRssi = aTlv.GetAverageRssi(); + mLastRssi = aTlv.GetLastRssi(); + mFrameErrorRate = aTlv.GetFrameErrorRate(); + mMessageErrorRate = aTlv.GetMessageErrorRate(); +} + } // namespace Utils } // namespace ot diff --git a/src/core/utils/mesh_diag.hpp b/src/core/utils/mesh_diag.hpp index 2f1768a05..529236ea5 100644 --- a/src/core/utils/mesh_diag.hpp +++ b/src/core/utils/mesh_diag.hpp @@ -38,6 +38,10 @@ #if OPENTHREAD_CONFIG_MESH_DIAG_ENABLE && OPENTHREAD_FTD +#if !OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE +#error "OPENTHREAD_CONFIG_MESH_DIAG_ENABLE requires OPENTHREAD_CONFIG_TMF_NETDIAG_CLIENT_ENABLE" +#endif + #include #include "coap/coap.hpp" @@ -46,6 +50,7 @@ #include "common/message.hpp" #include "common/timer.hpp" #include "net/ip6_address.hpp" +#include "thread/network_diagnostic.hpp" #include "thread/network_diagnostic_tlvs.hpp" struct otMeshDiagIp6AddrIterator @@ -65,14 +70,19 @@ namespace Utils { */ class MeshDiag : public InstanceLocator { + friend class ot::NetworkDiagnostic::Client; + public: static constexpr uint16_t kVersionUnknown = OT_MESH_DIAG_VERSION_UNKNOWN; ///< Unknown version. - typedef otMeshDiagDiscoverConfig DiscoverConfig; ///< The discovery configuration. - typedef otMeshDiagDiscoverCallback DiscoverCallback; ///< The discovery callback function pointer type. + typedef otMeshDiagDiscoverConfig DiscoverConfig; ///< Discovery configuration. + typedef otMeshDiagDiscoverCallback DiscoverCallback; ///< Discovery callback. + typedef otMeshDiagQueryChildTableCallback QueryChildTableCallback; ///< Query Child Table callback. + typedef otMeshDiagChildIp6AddrsCallback ChildIp6AddrsCallback; ///< Child IPv6 addresses callback. + typedef otMeshDiagQueryRouterNeighborTableCallback RouterNeighborTableCallback; ///< Neighbor table callback. /** - * Represents an iterator to go over list of IPv6 addresses of a router. + * Represents an iterator to go over list of IPv6 addresses of a router or an MTD child. * */ class Ip6AddrIterator : public otMeshDiagIp6AddrIterator @@ -164,7 +174,7 @@ public: * @param[in] aContext A context to pass in @p aCallback. * * @retval kErrorNone The network topology discovery started successfully. - * @retval kErrorBusy A previous discovery request is still ongoing. + * @retval kErrorBusy A previous discovery or query request is still ongoing. * @retval kErrorInvalidState Device is not attached. * @retval kErrorNoBufs Could not allocate buffer to send discovery messages. * @@ -172,9 +182,58 @@ public: Error DiscoverTopology(const DiscoverConfig &aConfig, DiscoverCallback aCallback, void *aContext); /** - * Cancels an ongoing topology discovery if there one, otherwise no action. + * Starts query for child table for a given router. * - * When ongoing discovery is cancelled, the callback from `DiscoverTopology()` will not be called anymore. + * @param[in] aRloc16 The RLOC16 of router to query. + * @param[in] aCallback The callback to report the queried child table. + * @param[in] aContext A context to pass in @p aCallback. + * + * @retval kErrorNone The query started successfully. + * @retval kErrorBusy A previous discovery or query request is still ongoing. + * @retval kErrorInvalidArgs The @p aRloc16 is not a valid router RLOC16. + * @retval kErrorInvalidState Device is not attached. + * @retval kErrorNoBufs Could not allocate buffer to send query messages. + * + */ + Error QueryChildTable(uint16_t aRloc16, QueryChildTableCallback aCallback, void *aContext); + + /** + * Sends a query to a parent to retrieve the IPv6 addresses of all its MTD children. + * + * @param[in] aRloc16 The RLOC16 of parent to query. + * @param[in] aCallback The callback to report the queried child IPv6 address list. + * @param[in] aContext A context to pass in @p aCallback. + * + * @retval kErrorNone The query started successfully. + * @retval kErrorBusy A previous discovery or query request is still ongoing. + * @retval kErrorInvalidArgs The @p aRloc16 is not a valid RLOC16. + * @retval kErrorInvalidState Device is not attached. + * @retval kErrorNoBufs Could not allocate buffer to send query messages. + * + */ + Error QueryChildrenIp6Addrs(uint16_t aRloc16, ChildIp6AddrsCallback aCallback, void *aContext); + + /** + * Starts query for router neighbor table for a given router. + * + * @param[in] aRloc16 The RLOC16 of router to query. + * @param[in] aCallback The callback to report the queried table. + * @param[in] aContext A context to pass in @p aCallback. + * + * @retval kErrorNone The query started successfully. + * @retval kErrorBusy A previous discovery or query request is still ongoing. + * @retval kErrorInvalidArgs The @p aRloc16 is not a valid router RLOC16. + * @retval kErrorInvalidState Device is not attached. + * @retval kErrorNoBufs Could not allocate buffer to send query messages. + * + */ + Error QueryRouterNeighborTable(uint16_t aRloc16, RouterNeighborTableCallback aCallback, void *aContext); + + /** + * Cancels an ongoing discovery or query operation if there one, otherwise no action. + * + * When ongoing discovery is cancelled, the callback from `DiscoverTopology()` or `QueryChildTable()` will not be + * called anymore. * */ void Cancel(void); @@ -184,9 +243,65 @@ private: static constexpr uint32_t kResponseTimeout = OPENTHREAD_CONFIG_MESH_DIAG_RESPONSE_TIMEOUT; - Error SendDiagGetTo(uint16_t aRloc16, const DiscoverConfig &aConfig); + enum State : uint8_t + { + kStateIdle, + kStateDicoverTopology, + kStateQueryChildTable, + kStateQueryChildrenIp6Addrs, + kStateQueryRouterNeighborTable, + }; + + struct DiscoverInfo + { + Callback mCallback; + Mle::RouterIdSet mExpectedRouterIdSet; + }; + + struct QueryChildTableInfo + { + Callback mCallback; + uint16_t mRouterRloc16; + }; + + struct QueryChildrenIp6AddrsInfo + { + Callback mCallback; + uint16_t mParentRloc16; + }; + + struct QueryRouterNeighborTableInfo + { + Callback mCallback; + uint16_t mRouterRloc16; + }; + + class ChildEntry : public otMeshDiagChildEntry + { + friend class MeshDiag; + + private: + void SetFrom(const NetworkDiagnostic::ChildTlv &aChildTlv); + }; + + class RouterNeighborEntry : public otMeshDiagRouterNeighborEntry + { + friend class MeshDiag; + + private: + void SetFrom(const NetworkDiagnostic::RouterNeighborTlv &aTlv); + }; + + Error SendQuery(uint16_t aRloc16, const uint8_t *aTlvs, uint8_t aTlvsLength); + void Finalize(Error aError); void HandleTimer(void); - void HandleDiagGetResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult); + bool HandleDiagnosticGetAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + Error ProcessMessage(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo, uint16_t aSenderRloc16); + bool ProcessChildTableAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + bool ProcessChildrenIp6AddrsAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + bool ProcessRouterNeighborTableAnswer(Coap::Message &aMessage, const Ip6::MessageInfo &aMessageInfo); + + void HandleDiagGetResponse(Coap::Message *aMessage, const Ip6::MessageInfo *aMessageInfo, Error aResult); static void HandleDiagGetResponse(void *aContext, otMessage *aMessage, @@ -195,9 +310,18 @@ private: using TimeoutTimer = TimerMilliIn; - Callback mDiscoverCallback; - Mle::RouterIdSet mExpectedRouterIdSet; - TimeoutTimer mTimer; + State mState; + uint16_t mExpectedQueryId; + uint16_t mExpectedAnswerIndex; + TimeoutTimer mTimer; + + union + { + DiscoverInfo mDiscover; + QueryChildTableInfo mQueryChildTable; + QueryChildrenIp6AddrsInfo mQueryChildrenIp6Addrs; + QueryRouterNeighborTableInfo mQueryRouterNeighborTable; + }; }; } // namespace Utils diff --git a/tests/scripts/thread-cert/Cert_5_7_02_CoapDiagCommands.py b/tests/scripts/thread-cert/Cert_5_7_02_CoapDiagCommands.py index 0e89e8375..4cdc83635 100755 --- a/tests/scripts/thread-cert/Cert_5_7_02_CoapDiagCommands.py +++ b/tests/scripts/thread-cert/Cert_5_7_02_CoapDiagCommands.py @@ -250,7 +250,7 @@ class Cert_5_7_02_CoapDiagCommands(thread_cert.TestCase): pkts.filter_wpan_src64(LEADER).\ filter_ipv6_dst(DUT_RLOC).\ filter_coap_request(DIAG_GET_URI).\ - filter(lambda p: dut_payload_tlvs == set(p.thread_diagnostic.tlv.type)).\ + filter(lambda p: dut_payload_tlvs <= set(p.thread_diagnostic.tlv.type)).\ must_next() dut_payload_tlvs.remove(DG_TYPE_LIST_TLV) dut_payload_tlvs.remove(DG_ROUTE64_TLV) @@ -277,7 +277,7 @@ class Cert_5_7_02_CoapDiagCommands(thread_cert.TestCase): filter(lambda p: { DG_TYPE_LIST_TLV, DG_MAC_COUNTERS_TLV - } == set(p.thread_diagnostic.tlv.type) + } <= set(p.thread_diagnostic.tlv.type) ).\ must_next() pkts.filter_wpan_src64(DUT).\ @@ -285,7 +285,7 @@ class Cert_5_7_02_CoapDiagCommands(thread_cert.TestCase): filter_coap_ack(DIAG_GET_URI).\ filter(lambda p: { DG_MAC_COUNTERS_TLV - } == set(p.thread_diagnostic.tlv.type) + } <= set(p.thread_diagnostic.tlv.type) ).\ must_next() @@ -304,7 +304,7 @@ class Cert_5_7_02_CoapDiagCommands(thread_cert.TestCase): DG_TYPE_LIST_TLV, DG_TIMEOUT_TLV, DG_CHILD_TABLE_TLV - } == set(p.thread_diagnostic.tlv.type) + } <= set(p.thread_diagnostic.tlv.type) ).\ must_next() pkts.filter_wpan_src64(DUT).\ @@ -327,7 +327,7 @@ class Cert_5_7_02_CoapDiagCommands(thread_cert.TestCase): DG_TYPE_LIST_TLV, DG_BATTERY_LEVEL_TLV, DG_SUPPLY_VOLTAGE_TLV - } == set(p.thread_diagnostic.tlv.type) + } <= set(p.thread_diagnostic.tlv.type) ).\ must_next() pkts.filter_wpan_src64(DUT).\ @@ -347,7 +347,7 @@ class Cert_5_7_02_CoapDiagCommands(thread_cert.TestCase): filter(lambda p: { DG_TYPE_LIST_TLV, DG_MAC_COUNTERS_TLV - } == set(p.thread_diagnostic.tlv.type) + } <= set(p.thread_diagnostic.tlv.type) ).\ must_next() pkts.filter_wpan_src64(DUT).\ @@ -370,7 +370,7 @@ class Cert_5_7_02_CoapDiagCommands(thread_cert.TestCase): filter(lambda p: { DG_TYPE_LIST_TLV, DG_MAC_COUNTERS_TLV - } == set(p.thread_diagnostic.tlv.type) + } <= set(p.thread_diagnostic.tlv.type) ).\ must_next() pkts.filter_wpan_src64(DUT).\ @@ -378,7 +378,7 @@ class Cert_5_7_02_CoapDiagCommands(thread_cert.TestCase): filter_coap_ack(DIAG_GET_URI).\ filter(lambda p: { DG_MAC_COUNTERS_TLV - } == set(p.thread_diagnostic.tlv.type) + } <= set(p.thread_diagnostic.tlv.type) ).\ must_next() @@ -401,7 +401,7 @@ class Cert_5_7_02_CoapDiagCommands(thread_cert.TestCase): pkts.filter_wpan_src64(LEADER).\ filter_ipv6_dst(REALM_LOCAL_All_THREAD_NODES_MULTICAST_ADDRESS).\ filter_coap_request(DIAG_GET_QRY_URI).\ - filter(lambda p: dut_payload_tlvs == set(p.thread_diagnostic.tlv.type)).\ + filter(lambda p: dut_payload_tlvs <= set(p.thread_diagnostic.tlv.type)).\ must_next() dut_payload_tlvs.remove(DG_TYPE_LIST_TLV) diff --git a/tests/scripts/thread-cert/Cert_5_7_03_CoapDiagCommands.py b/tests/scripts/thread-cert/Cert_5_7_03_CoapDiagCommands.py index bf2482673..6e4231605 100755 --- a/tests/scripts/thread-cert/Cert_5_7_03_CoapDiagCommands.py +++ b/tests/scripts/thread-cert/Cert_5_7_03_CoapDiagCommands.py @@ -189,7 +189,7 @@ class Cert_5_7_03_CoapDiagCommands_Base(thread_cert.TestCase): } if self.TOPOLOGY[ROUTER1]['name'] == 'DUT': dut_payload_tlvs.update({DG_CONNECTIVITY_TLV, DG_ROUTE64_TLV, DG_CHILD_TABLE_TLV}) - _qr_pkt.must_verify(lambda p: dut_payload_tlvs == set(p.thread_diagnostic.tlv.type)) + _qr_pkt.must_verify(lambda p: dut_payload_tlvs <= set(p.thread_diagnostic.tlv.type)) # Step 3: The DUT automatically responds with a DIAG_GET.ans response # MUST contain the requested diagnostic TLVs: @@ -208,7 +208,7 @@ class Cert_5_7_03_CoapDiagCommands_Base(thread_cert.TestCase): filter_ipv6_dst(LEADER_RLOC).\ filter_coap_request(DIAG_GET_ANS_URI).\ filter(lambda p: - dut_payload_tlvs == set(p.thread_diagnostic.tlv.type) and\ + dut_payload_tlvs <= set(p.thread_diagnostic.tlv.type) and\ {str(p.wpan.src64), colon_hex(dut_addr16, 2), '0f'} < set(p.thread_diagnostic.tlv.general) ).\ @@ -221,7 +221,7 @@ class Cert_5_7_03_CoapDiagCommands_Base(thread_cert.TestCase): filter_ipv6_dst(REALM_LOCAL_All_THREAD_NODES_MULTICAST_ADDRESS).\ filter_coap_request(DIAG_GET_QRY_URI).\ filter(lambda p: - dut_payload_tlvs == set(p.thread_diagnostic.tlv.type) + dut_payload_tlvs <= set(p.thread_diagnostic.tlv.type) ).\ must_next() @@ -240,7 +240,7 @@ class Cert_5_7_03_CoapDiagCommands_Base(thread_cert.TestCase): filter_ipv6_dst(LEADER_RLOC).\ filter_coap_request(DIAG_GET_ANS_URI).\ filter(lambda p: - dut_payload_tlvs == set(p.thread_diagnostic.tlv.type) and\ + dut_payload_tlvs <= set(p.thread_diagnostic.tlv.type) and\ {str(p.wpan.src64), colon_hex(dut_addr16, 2), '0f'} < set(p.thread_diagnostic.tlv.general) ).\ diff --git a/tests/toranj/cli/test-023-mesh-diag.py b/tests/toranj/cli/test-023-mesh-diag.py new file mode 100755 index 000000000..a62475c25 --- /dev/null +++ b/tests/toranj/cli/test-023-mesh-diag.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from cli import verify +from cli import verify_within +import cli +import time + +# ----------------------------------------------------------------------------------------------------------------------- +# Test description: Validate Mesh Diag module commands. +# +# Network topology +# +# r1 ---- r2 ---- r3 +# /\ /|\ +# / \ / | \ +# f1 s1 f2 s2 s3 + +test_name = __file__[:-3] if __file__.endswith('.py') else __file__ +print('-' * 120) +print('Starting \'{}\''.format(test_name)) + +# ----------------------------------------------------------------------------------------------------------------------- +# Creating `cli.Node` instances + +speedup = 40 +cli.Node.set_time_speedup_factor(speedup) + +r1 = cli.Node() +r2 = cli.Node() +r3 = cli.Node() +fed1 = cli.Node() +fed2 = cli.Node() +sed1 = cli.Node() +sed2 = cli.Node() +sed3 = cli.Node() + +# ----------------------------------------------------------------------------------------------------------------------- +# Form topology + +r1.allowlist_node(r2) +r1.allowlist_node(fed1) +r1.allowlist_node(sed1) + +r2.allowlist_node(r1) +r2.allowlist_node(r3) + +r3.allowlist_node(r2) +r3.allowlist_node(fed2) +r3.allowlist_node(sed2) +r3.allowlist_node(sed3) + +fed1.allowlist_node(r1) +fed2.allowlist_node(r3) +sed1.allowlist_node(r1) +sed2.allowlist_node(r3) +sed3.allowlist_node(r3) + +r1.form('mesh-diag') + +r1.add_prefix('fd00:1::/64', 'poas') +r1.add_prefix('fd00:2::/64', 'poas') +r1.register_netdata() + +r2.join(r1) +r3.join(r1) +fed1.join(r1, cli.JOIN_TYPE_REED) +fed2.join(r1, cli.JOIN_TYPE_REED) +sed1.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE) +sed2.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE) +sed3.join(r1, cli.JOIN_TYPE_SLEEPY_END_DEVICE) + +verify(r1.get_state() == 'leader') +verify(r2.get_state() == 'router') +verify(r3.get_state() == 'router') +verify(fed1.get_state() == 'child') +verify(fed2.get_state() == 'child') +verify(sed1.get_state() == 'child') +verify(sed2.get_state() == 'child') +verify(sed3.get_state() == 'child') + +# ----------------------------------------------------------------------------------------------------------------------- +# Test Implementation + +r1_rloc = int(r1.get_rloc16(), 16) +r2_rloc = int(r2.get_rloc16(), 16) +r3_rloc = int(r3.get_rloc16(), 16) + +fed1_rloc = int(fed1.get_rloc16(), 16) +fed2_rloc = int(fed2.get_rloc16(), 16) + +sed1_rloc = int(sed1.get_rloc16(), 16) +sed2_rloc = int(sed2.get_rloc16(), 16) +sed3_rloc = int(sed3.get_rloc16(), 16) + +topo = r1.cli('meshdiag topology') +verify(len(topo) == 6) + +routers = [] +for line in topo: + if line.startswith('id:'): + routers.append(int(line.split(' ')[1].split(':')[1], 16)) + +verify(r1_rloc in routers) +verify(r2_rloc in routers) +verify(r3_rloc in routers) + +r2.cli('meshdiag topology ip6-addrs') +r3.cli('meshdiag topology ip6-addrs children') +r1.cli('meshdiag topology children') + +childtable = r2.cli('meshdiag childtable', r1_rloc) +verify(len([line for line in childtable if line.startswith('rloc16')]) == 2) + +childtable = r1.cli('meshdiag childtable', r3_rloc) +verify(len([line for line in childtable if line.startswith('rloc16')]) == 3) + +childtable = r1.cli('meshdiag childtable', r2_rloc) +verify(len([line for line in childtable if line.startswith('rloc16')]) == 0) + +childrenip6addrs = r2.cli('meshdiag childip6', r1_rloc) +verify(len([line for line in childrenip6addrs if line.startswith('child-rloc16')]) == 1) +verify(len([line for line in childrenip6addrs if line.startswith(' ')]) == 3) + +childrenip6addrs = r1.cli('meshdiag childip6', r3_rloc) +verify(len([line for line in childrenip6addrs if line.startswith('child-rloc16')]) == 2) + +neightable = r1.cli('meshdiag routerneighbortable', r2_rloc) +verify(len([line for line in neightable if line.startswith('rloc16')]) == 2) + +neightable = r3.cli('meshdiag routerneighbortable', r1_rloc) +verify(len([line for line in neightable if line.startswith('rloc16')]) == 1) + +# ----------------------------------------------------------------------------------------------------------------------- +# Test finished + +cli.Node.finalize_all_nodes() + +print('\'{}\' passed.'.format(test_name)) diff --git a/tests/toranj/start.sh b/tests/toranj/start.sh index 587f7557e..33766ae4e 100755 --- a/tests/toranj/start.sh +++ b/tests/toranj/start.sh @@ -187,6 +187,7 @@ if [ "$TORANJ_CLI" = 1 ]; then run cli/test-020-net-diag-vendor-info.py run cli/test-021-br-route-prf.py run cli/test-022-netdata-full.py + run cli/test-023-mesh-diag.py run cli/test-400-srp-client-server.py run cli/test-601-channel-manager-channel-change.py # Skip the "channel-select" test on a TREL only radio link, since it