diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 462f12355..c53d6a2b0 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (489) +#define OPENTHREAD_API_VERSION (490) /** * @addtogroup api-instance diff --git a/include/openthread/netdiag.h b/include/openthread/netdiag.h index 762f81dff..98e1450e5 100644 --- a/include/openthread/netdiag.h +++ b/include/openthread/netdiag.h @@ -77,6 +77,7 @@ extern "C" { #define OT_NETWORK_DIAGNOSTIC_TLV_QUERY_ID 33 ///< Query ID TLV #define OT_NETWORK_DIAGNOSTIC_TLV_MLE_COUNTERS 34 ///< MLE Counters TLV #define OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_APP_URL 35 ///< Vendor App URL TLV +#define OT_NETWORK_DIAGNOSTIC_TLV_ENHANCED_ROUTE 37 ///< Enhanced Route TLV #define OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_NAME_TLV_LENGTH 32 ///< Max length of Vendor Name TLV. #define OT_NETWORK_DIAGNOSTIC_MAX_VENDOR_MODEL_TLV_LENGTH 32 ///< Max length of Vendor Model TLV. @@ -128,6 +129,51 @@ typedef struct otNetworkDiagRoute otNetworkDiagRouteData mRouteData[OT_NETWORK_MAX_ROUTER_ID + 1]; ///< Link Quality and Routing Cost data. } otNetworkDiagRoute; +/** + * Represents a Network Diagnostic Enhanced Route data. + */ +typedef struct otNetworkDiagEnhRouteData +{ + uint8_t mRouterId; ///< The Router ID. + bool mIsSelf : 1; ///< This is the queried device itself. If set, the other fields should be ignored. + bool mHasLink : 1; ///< Indicates whether the queried device has a direct link with router. + uint8_t mLinkQualityOut : 2; ///< Link Quality Out (applicable when `mHasLink`). + uint8_t mLinkQualityIn : 2; ///< Link Quality In (applicable when `mHasLink`). + + /** + * The next hop Router ID tracked towards this router. + * + * This field indicates the next hop router towards `mRouterId` when using multi-hop forwarding. + * + * If the device has no direct link with the router (`mHasLink == false`), this field indicates the next hop router + * that would be used to forward messages destined to `mRouterId`. + * + * If the device has a direct link with the router (`mHasLink == true`), this field indicates the alternate + * multi-hop path that may be used. Note that whether the direct link or this alternate path through the next hop + * is used to forward messages depends on their associated total path costs. + * + * If there is no next hop, then `OT_NETWORK_MAX_ROUTER_ID + 1` is used. + */ + uint8_t mNextHop; + + /** + * The route cost associated with forwarding to `mRouterId` using `mNextHop` (when valid). + * + * This is the route cost `mNextHop` has claimed to have towards `mRouterId`. Importantly, it does not include the + * link cost to send to `mNextHop` itself. + */ + uint8_t mNextHopCost; +} otNetworkDiagEnhRouteData; + +/** + * Represents a Network Diagnostic Enhanced Route TLV value. + */ +typedef struct otNetworkDiagEnhRoute +{ + uint8_t mRouteCount; ///< Number of `mRouteData` entries. + otNetworkDiagEnhRouteData mRouteData[OT_NETWORK_MAX_ROUTER_ID + 1]; ///< Route Data per router. +} otNetworkDiagEnhRoute; + /** * Represents a Network Diagnostic Mac Counters value. * @@ -195,6 +241,7 @@ typedef struct otNetworkDiagTlv uint32_t mTimeout; otNetworkDiagConnectivity mConnectivity; otNetworkDiagRoute mRoute; + otNetworkDiagEnhRoute mEnhRoute; otLeaderData mLeaderData; otNetworkDiagMacCounters mMacCounters; otNetworkDiagMleCounters mMleCounters; diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index b1ec724a1..25eb15a94 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -7555,6 +7555,7 @@ template <> otError Interpreter::Process(Arg aArgs[]) * - `29`: Child TLV * - `34`: MLE Counters TLV * - `35`: Vendor App URL TLV + * - `37`: Enhanced Route TLV * @par * Sends a network diagnostic request to retrieve specified Type Length Values (TLVs) * for the specified addresses(es). @@ -7663,6 +7664,10 @@ void Interpreter::HandleDiagnosticGetResponse(otError aError, OutputLine("Route:"); OutputRoute(kIndentSize, diagTlv.mData.mRoute); break; + case OT_NETWORK_DIAGNOSTIC_TLV_ENHANCED_ROUTE: + OutputLine("EnhRoute:"); + OutputEnhRoute(kIndentSize, diagTlv.mData.mEnhRoute); + break; case OT_NETWORK_DIAGNOSTIC_TLV_LEADER_DATA: OutputLine("Leader Data:"); OutputLeaderData(kIndentSize, diagTlv.mData.mLeaderData); @@ -7756,6 +7761,7 @@ void Interpreter::OutputConnectivity(uint8_t aIndentSize, const otNetworkDiagCon OutputLine(aIndentSize, "SedBufferSize: %u", aConnectivity.mSedBufferSize); OutputLine(aIndentSize, "SedDatagramCount: %u", aConnectivity.mSedDatagramCount); } + void Interpreter::OutputRoute(uint8_t aIndentSize, const otNetworkDiagRoute &aRoute) { OutputLine(aIndentSize, "IdSequence: %u", aRoute.mIdSequence); @@ -7778,6 +7784,36 @@ void Interpreter::OutputRouteData(uint8_t aIndentSize, const otNetworkDiagRouteD OutputLine(aIndentSize, "RouteCost: %u", aRouteData.mRouteCost); } +void Interpreter::OutputEnhRoute(uint8_t aIndentSize, const otNetworkDiagEnhRoute &aEnhRoute) +{ + static constexpr uint8_t kInvalidRouterId = OT_NETWORK_MAX_ROUTER_ID + 1; + + for (uint8_t index = 0; index < aEnhRoute.mRouteCount; index++) + { + const otNetworkDiagEnhRouteData &routeData = aEnhRoute.mRouteData[index]; + + OutputFormat(aIndentSize, "- RouterId:%-2u", routeData.mRouterId); + + if (routeData.mIsSelf) + { + OutputLine(" The queried device"); + continue; + } + + OutputFormat(" HasLink:%-3s LinkQualityOut:%u LinkQualityIn:%u ", routeData.mHasLink ? "yes" : "no", + routeData.mLinkQualityOut, routeData.mLinkQualityIn); + + if (routeData.mNextHop == kInvalidRouterId) + { + OutputLine("NextHop:na NextHopCost:na"); + } + else + { + OutputLine("NextHop:%-2u NextHopCost:%u", routeData.mNextHop, routeData.mNextHopCost); + } + } +} + void Interpreter::OutputLeaderData(uint8_t aIndentSize, const otLeaderData &aLeaderData) { OutputLine(aIndentSize, "PartitionId: 0x%08lx", ToUlong(aLeaderData.mPartitionId)); diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 71a4b1b21..46d714577 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -262,6 +262,7 @@ private: void OutputConnectivity(uint8_t aIndentSize, const otNetworkDiagConnectivity &aConnectivity); void OutputRoute(uint8_t aIndentSize, const otNetworkDiagRoute &aRoute); void OutputRouteData(uint8_t aIndentSize, const otNetworkDiagRouteData &aRouteData); + void OutputEnhRoute(uint8_t aIndentSize, const otNetworkDiagEnhRoute &aEnhRoute); void OutputLeaderData(uint8_t aIndentSize, const otLeaderData &aLeaderData); void OutputNetworkDiagMacCounters(uint8_t aIndentSize, const otNetworkDiagMacCounters &aMacCounters); void OutputNetworkDiagMleCounters(uint8_t aIndentSize, const otNetworkDiagMleCounters &aMleCounters); diff --git a/src/core/thread/network_diagnostic.cpp b/src/core/thread/network_diagnostic.cpp index 7f879c4bd..ed3c14485 100644 --- a/src/core/thread/network_diagnostic.cpp +++ b/src/core/thread/network_diagnostic.cpp @@ -196,6 +196,47 @@ Error Server::AppendChildTable(Message &aMessage) exit: return error; } + +Error Server::AppendEnhancedRoute(Message &aMessage) +{ + Error error = kErrorNone; + Tlv tlv; + Mle::RouterIdSet routerIdSet; + EnhancedRouteTlvEntry entry; + + VerifyOrExit(Get().IsRouterOrLeader()); + + Get().GetRouterIdSet(routerIdSet); + + tlv.SetType(Tlv::kEnhancedRoute); + tlv.SetLength(sizeof(Mle::RouterIdSet) + routerIdSet.GetNumberOfAllocatedIds() * sizeof(entry)); + + SuccessOrExit(error = aMessage.Append(tlv)); + SuccessOrExit(error = aMessage.Append(routerIdSet)); + + for (uint8_t routerId = 0; routerId <= Mle::kMaxRouterId; routerId++) + { + if (!routerIdSet.Contains(routerId)) + { + continue; + } + + if (Get().MatchesRouterId(routerId)) + { + entry.InitAsSelf(); + } + else + { + entry.InitFrom(*Get().FindRouterById(routerId)); + } + + SuccessOrExit(error = aMessage.Append(entry)); + } + +exit: + return error; +} + #endif // OPENTHREAD_FTD Error Server::AppendMacCounters(Message &aMessage) @@ -368,6 +409,10 @@ Error Server::AppendDiagTlv(uint8_t aTlvType, Message &aMessage) break; } + case Tlv::kEnhancedRoute: + error = AppendEnhancedRoute(aMessage); + break; + case Tlv::kChildTable: error = AppendChildTable(aMessage); break; @@ -1000,6 +1045,51 @@ static void ParseRoute(const RouteTlv &aRouteTlv, otNetworkDiagRoute &aNetworkDi aNetworkDiagRoute.mIdSequence = aRouteTlv.GetRouterIdSequence(); } +static Error ParseEnhancedRoute(const Message &aMessage, uint16_t aOffset, otNetworkDiagEnhRoute &aNetworkDiagEnhRoute) +{ + Error error; + OffsetRange offsetRange; + Tlv tlv; + Mle::RouterIdSet routerIdSet; + uint8_t index; + + SuccessOrExit(error = aMessage.Read(aOffset, tlv)); + + VerifyOrExit(!tlv.IsExtended(), error = kErrorParse); + VerifyOrExit(tlv.GetType() == Tlv::kEnhancedRoute, error = kErrorParse); + + aOffset += sizeof(tlv); + offsetRange.Init(aOffset, tlv.GetLength()); + + SuccessOrExit(error = aMessage.Read(offsetRange, routerIdSet)); + offsetRange.AdvanceOffset(sizeof(routerIdSet)); + + index = 0; + + for (uint8_t routerId = 0; routerId <= Mle::kMaxRouterId; routerId++) + { + EnhancedRouteTlvEntry entry; + + if (!routerIdSet.Contains(routerId)) + { + continue; + } + + SuccessOrExit(error = aMessage.Read(offsetRange, entry)); + offsetRange.AdvanceOffset(sizeof(entry)); + + aNetworkDiagEnhRoute.mRouteData[index].mRouterId = routerId; + entry.Parse(aNetworkDiagEnhRoute.mRouteData[index]); + + index++; + } + + aNetworkDiagEnhRoute.mRouteCount = index; + +exit: + return error; +} + static inline void ParseMacCounters(const MacCountersTlv &aMacCountersTlv, otNetworkDiagMacCounters &aMacCounters) { aMacCounters.mIfInUnknownProtos = aMacCountersTlv.GetIfInUnknownProtos(); @@ -1092,6 +1182,10 @@ Error Client::GetNextDiagTlv(const Coap::Message &aMessage, Iterator &aIterator, break; } + case Tlv::kEnhancedRoute: + SuccessOrExit(error = ParseEnhancedRoute(aMessage, offset, aTlvInfo.mData.mEnhRoute)); + break; + case Tlv::kLeaderData: { LeaderDataTlv leaderDataTlv; diff --git a/src/core/thread/network_diagnostic.hpp b/src/core/thread/network_diagnostic.hpp index 23ed352f6..beeab1ae6 100644 --- a/src/core/thread/network_diagnostic.hpp +++ b/src/core/thread/network_diagnostic.hpp @@ -205,6 +205,7 @@ private: Error AppendRouterNeighborTlvs(Coap::Message *&aAnswer, AnswerInfo &aInfo); Error AppendChildTableIp6AddressList(Coap::Message *&aAnswer, AnswerInfo &aInfo); Error AppendChildIp6AddressListTlv(Coap::Message &aAnswer, const Child &aChild); + Error AppendEnhancedRoute(Message &aMessage); static void HandleAnswerResponse(void *aContext, otMessage *aMessage, diff --git a/src/core/thread/network_diagnostic_tlvs.cpp b/src/core/thread/network_diagnostic_tlvs.cpp index 9d4fc6651..e4141547f 100644 --- a/src/core/thread/network_diagnostic_tlvs.cpp +++ b/src/core/thread/network_diagnostic_tlvs.cpp @@ -38,6 +38,35 @@ namespace ot { namespace NetworkDiagnostic { +void EnhancedRouteTlvEntry::InitFrom(const Router &aRouter) +{ + uint16_t data = 0; + + if (aRouter.IsStateValid()) + { + data |= kLinkFlag; + data |= (static_cast(aRouter.GetLinkQualityOut()) << kLinkQualityOutOffset); + data |= (static_cast(aRouter.GetLinkQualityIn()) << kLinkQualityInOffset); + } + + data |= ((static_cast(aRouter.GetNextHop()) & kNextHopMask) << kNextHopOffset); + data |= ((static_cast(aRouter.GetCost()) & kCostMask) << kNextHopCostOffset); + + SetRouteData(data); +} + +void EnhancedRouteTlvEntry::Parse(ParseInfo &aParseInfo) const +{ + uint16_t data = GetRouteData(); + + aParseInfo.mIsSelf = (data & kSelfFlag); + aParseInfo.mHasLink = (data & kLinkFlag); + aParseInfo.mLinkQualityOut = static_cast((data >> kLinkQualityOutOffset) & kLinkQualityMask); + aParseInfo.mLinkQualityIn = static_cast((data >> kLinkQualityInOffset) & kLinkQualityMask); + aParseInfo.mNextHop = static_cast((data >> kNextHopOffset) & kNextHopMask); + aParseInfo.mNextHopCost = static_cast((data >> kNextHopCostOffset) & kCostMask); +} + #if OPENTHREAD_FTD void ChildTlv::InitFrom(const Child &aChild) diff --git a/src/core/thread/network_diagnostic_tlvs.hpp b/src/core/thread/network_diagnostic_tlvs.hpp index 4776c798d..08d9e81c8 100644 --- a/src/core/thread/network_diagnostic_tlvs.hpp +++ b/src/core/thread/network_diagnostic_tlvs.hpp @@ -95,6 +95,7 @@ public: kQueryId = OT_NETWORK_DIAGNOSTIC_TLV_QUERY_ID, kMleCounters = OT_NETWORK_DIAGNOSTIC_TLV_MLE_COUNTERS, kVendorAppUrl = OT_NETWORK_DIAGNOSTIC_TLV_VENDOR_APP_URL, + kEnhancedRoute = OT_NETWORK_DIAGNOSTIC_TLV_ENHANCED_ROUTE, }; /** @@ -954,6 +955,59 @@ private: #endif // OPENTHREAD_FTD +/** + * Represents an Enhanced Route TLV Entry + */ +OT_TOOL_PACKED_BEGIN +class EnhancedRouteTlvEntry +{ +public: + typedef otNetworkDiagEnhRouteData ParseInfo; ///< Parse entry info + + /** + * Initializes the entry as self (associated with device itself). + */ + void InitAsSelf(void) { SetRouteData(kSelfFlag); } + + /** + * Initializes the entry from given `router`. + * + * @param[in] aRouter Router entry to use for initialization. + */ + void InitFrom(const Router &aRouter); + + /** + * Parses the entry and populate the information in given `ParseInfo` struct. + * + * @parma[out] aParseInfo The `ParseInfo` structure to populate. + */ + void Parse(ParseInfo &aParseInfo) const; + +private: + // Format: + // + // 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + // | S | L | LQOut | LQIn | NextHop (6-bit) | NHCost(4 bits)| + // +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ + + static constexpr uint16_t kSelfFlag = 1 << 15; + static constexpr uint16_t kLinkFlag = 1 << 14; + static constexpr uint8_t kLinkQualityOutOffset = 12; + static constexpr uint8_t kLinkQualityInOffset = 10; + static constexpr uint8_t kNextHopOffset = 4; + static constexpr uint8_t kNextHopCostOffset = 0; + + static constexpr uint16_t kLinkQualityMask = 0x3; + static constexpr uint16_t kNextHopMask = 0x3f; + static constexpr uint16_t kCostMask = 0xf; + + uint16_t GetRouteData(void) const { return BigEndian::HostSwap16(mRouteData); } + void SetRouteData(uint16_t aRouteData) { mRouteData = BigEndian::HostSwap16(aRouteData); } + + uint16_t mRouteData; +} OT_TOOL_PACKED_END; + /** * Implements Answer TLV generation and parsing. */ diff --git a/tests/toranj/cli/test-023-mesh-diag.py b/tests/toranj/cli/test-023-mesh-diag.py index 418813e43..1dfe53715 100755 --- a/tests/toranj/cli/test-023-mesh-diag.py +++ b/tests/toranj/cli/test-023-mesh-diag.py @@ -160,6 +160,50 @@ 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) +# Validate network diagnostics enhanced route TLV + +r1_router_id = int(r1_rloc / 1024) +r2_router_id = int(r2_rloc / 1024) +r3_router_id = int(r3_rloc / 1024) + +enh_routes = r1.cli('networkdiagnostic get', r3.get_rloc_ip_addr(), 37) + +verify(enh_routes[1] == 'EnhRoute:') +verify(len(enh_routes) == 5) + +for line in enh_routes[2:]: + line = line.strip() + verify(line.startswith('- RouterId:')) + rid = int(line[11:].split()[0]) + if (rid == r1_router_id): + verify('HasLink:no' in line) + verify(f'NextHop:{r2_router_id}' in line) + elif (rid == r2_router_id): + verify('HasLink:yes' in line) + verify('NextHop:na' in line) + elif (rid == r3_router_id): + verify('The queried device' in line) + else: + verify(False) + +enh_routes = r3.cli('networkdiagnostic get', r2.get_rloc_ip_addr(), 37) + +verify(enh_routes[1] == 'EnhRoute:') +verify(len(enh_routes) == 5) + +for line in enh_routes[2:]: + line = line.strip() + verify(line.startswith('- RouterId:')) + rid = int(line[11:].split()[0]) + if (rid == r1_router_id): + verify('HasLink:yes' in line) + elif (rid == r2_router_id): + verify('The queried device' in line) + elif (rid == r3_router_id): + verify('HasLink:yes' in line) + else: + verify(False) + # ----------------------------------------------------------------------------------------------------------------------- # Test finished