diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 9d559d49d..4c2565400 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 (615) +#define OPENTHREAD_API_VERSION (616) /** * @addtogroup api-instance diff --git a/include/openthread/srp_client.h b/include/openthread/srp_client.h index 4383e24b1..5749989ca 100644 --- a/include/openthread/srp_client.h +++ b/include/openthread/srp_client.h @@ -119,6 +119,33 @@ typedef struct otSrpClientService uint32_t mKeyLease; ///< Desired key lease interval in sec - zero to use default. } otSrpClientService; +/** + * Represents the SRP client counters. + */ +typedef struct otSrpClientCounters +{ + uint64_t mRegisteredTime; ///< Cumulative number of milliseconds spent in the registered state. + uint64_t mAnycastAvailableTime; ///< Subset of `mRegisteredTime` while registered with an anycast server. + uint64_t mUnicastAvailableTime; ///< Subset of `mRegisteredTime` while registered with a unicast server. + uint64_t mTrackedTime; ///< Cumulative milliseconds since last counter reset, across all client states (including + ///< stopped/paused). + uint32_t mTxUpdates; ///< Number of SRP update transmissions (wire-level; includes retransmissions). + uint32_t mUpdateAttempts; ///< Number of fresh SRP update transactions started. + uint32_t mSuccess; ///< Number of transactions completed with a successful server response. + uint32_t mRejectedDuplicate; ///< Number of transactions completed with a name/record conflict response. + uint32_t mRejectedSecurity; ///< Number of transactions completed with a security/policy/algorithm response. + uint32_t mRejectedOther; ///< Number of transactions completed with any other server error response. + uint32_t mTimeouts; ///< Number of retransmission timers that expired before a response arrived. + uint32_t mHostAddressChanges; ///< Number of host-address change events that triggered an SRP re-registration. + uint32_t mServerChanges; ///< Number of auto-start server (re-)selections that triggered an SRP update. + uint32_t mServiceAdds; ///< Number of times a service was successfully added. + uint32_t mServiceRemoves; ///< Number of times a service was removed (sends an unregister to server). + uint32_t mServiceClears; ///< Number of times a service was cleared (local-only, no server message). + uint32_t mHostAndServicesRemoves; ///< Number of times the host and all services were removed (notifies server). + uint32_t mHostAndServicesClears; ///< Number of times the host and all services were cleared (local-only). + uint32_t mTxTotalBytes; ///< Cumulative UDP payload bytes of transmitted SRP messages. +} otSrpClientCounters; + /** * Pointer type defines the callback used by SRP client to notify user of changes/events/errors. * @@ -675,6 +702,28 @@ void otSrpClientSetServiceKeyRecordEnabled(otInstance *aInstance, bool aEnabled) */ bool otSrpClientIsServiceKeyRecordEnabled(otInstance *aInstance); +/** + * Gets the SRP client counters. + * + * Requires `OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE` to be enabled. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns A pointer to the SRP client counters. + */ +const otSrpClientCounters *otSrpClientGetCounters(otInstance *aInstance); + +/** + * Resets the SRP client counters. + * + * Requires `OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE` to be enabled. + * + * All event counters are cleared and the time-tracking accumulators are restarted. + * + * @param[in] aInstance A pointer to an OpenThread instance. + */ +void otSrpClientResetCounters(otInstance *aInstance); + /** * @} */ diff --git a/src/cli/README_SRP_CLIENT.md b/src/cli/README_SRP_CLIENT.md index ca486f310..d51dd205e 100644 --- a/src/cli/README_SRP_CLIENT.md +++ b/src/cli/README_SRP_CLIENT.md @@ -7,6 +7,7 @@ Usage : `srp client [command] ...` - [help](#help) - [autostart](#autostart) - [callback](#callback) +- [counters](#counters) - [host](#host) - [keyleaseinterval](#keyleaseinterval) - [leaseinterval](#leaseinterval) @@ -127,6 +128,43 @@ Removed service list: instance:"ins1", name:"_test1._udp", state:Removed, port:777, priority:0, weight:0 ``` +### counters + +Usage: `srp client counters [reset]` + +Get the SRP client counters. Requires `OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE` to be enabled. + +```bash +> srp client counters +Tx Updates: 12 +Update Attempts: 11 +Success: 11 +Rejected Duplicate: 0 +Rejected Security: 0 +Rejected Other: 0 +Timeouts: 1 +Host Address Changes: 0 +Server Changes: 1 +Service Adds: 1 +Service Removes: 0 +Service Clears: 0 +Host And Services Removes: 0 +Host And Services Clears: 0 +Tx Total Bytes: 4380 +Registered Time Milli: 845321 +Anycast Available Time Milli: 0 +Unicast Available Time Milli: 901002 +Tracked Time Milli: 901002 +Done +``` + +Reset the SRP client counters. + +```bash +> srp client counters reset +Done +``` + ### host Usage: `srp client host` diff --git a/src/cli/cli_srp_client.cpp b/src/cli/cli_srp_client.cpp index a41927407..2f3ceaa90 100644 --- a/src/cli/cli_srp_client.cpp +++ b/src/cli/cli_srp_client.cpp @@ -179,6 +179,109 @@ exit: return error; } +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE +/** + * @cli srp client counters + * @code + * srp client counters + * Tx Updates: 12 + * Update Attempts: 11 + * Success: 11 + * Rejected Duplicate: 0 + * Rejected Security: 0 + * Rejected Other: 0 + * Timeouts: 1 + * Host Address Changes: 0 + * Server Changes: 1 + * Service Adds: 1 + * Service Removes: 0 + * Service Clears: 0 + * Host And Services Removes: 0 + * Host And Services Clears: 0 + * Tx Total Bytes: 4380 + * Registered Time Milli: 845321 + * Anycast Available Time Milli: 0 + * Unicast Available Time Milli: 901002 + * Tracked Time Milli: 901002 + * Done + * @endcode + * @par api_copy + * #otSrpClientGetCounters + */ +template <> otError SrpClient::Process(Arg aArgs[]) +{ + otError error = OT_ERROR_NONE; + + if (aArgs[0].IsEmpty()) + { + struct CounterEntry + { + const uint32_t otSrpClientCounters::*mValuePtr; + const char *mName; + }; + + struct TimeCounterEntry + { + const uint64_t otSrpClientCounters::*mValuePtr; + const char *mName; + }; + + static const CounterEntry kCounters[] = { + {&otSrpClientCounters::mTxUpdates, "Tx Updates"}, + {&otSrpClientCounters::mUpdateAttempts, "Update Attempts"}, + {&otSrpClientCounters::mSuccess, "Success"}, + {&otSrpClientCounters::mRejectedDuplicate, "Rejected Duplicate"}, + {&otSrpClientCounters::mRejectedSecurity, "Rejected Security"}, + {&otSrpClientCounters::mRejectedOther, "Rejected Other"}, + {&otSrpClientCounters::mTimeouts, "Timeouts"}, + {&otSrpClientCounters::mHostAddressChanges, "Host Address Changes"}, + {&otSrpClientCounters::mServerChanges, "Server Changes"}, + {&otSrpClientCounters::mServiceAdds, "Service Adds"}, + {&otSrpClientCounters::mServiceRemoves, "Service Removes"}, + {&otSrpClientCounters::mServiceClears, "Service Clears"}, + {&otSrpClientCounters::mHostAndServicesRemoves, "Host And Services Removes"}, + {&otSrpClientCounters::mHostAndServicesClears, "Host And Services Clears"}, + {&otSrpClientCounters::mTxTotalBytes, "Tx Total Bytes"}, + }; + + static const TimeCounterEntry kTimeCounters[] = { + {&otSrpClientCounters::mRegisteredTime, "Registered Time Milli"}, + {&otSrpClientCounters::mAnycastAvailableTime, "Anycast Available Time Milli"}, + {&otSrpClientCounters::mUnicastAvailableTime, "Unicast Available Time Milli"}, + {&otSrpClientCounters::mTrackedTime, "Tracked Time Milli"}, + }; + + const otSrpClientCounters *counters = otSrpClientGetCounters(GetInstancePtr()); + + for (const CounterEntry &entry : kCounters) + { + OutputLine("%s: %lu", entry.mName, ToUlong(counters->*entry.mValuePtr)); + } + + for (const TimeCounterEntry &entry : kTimeCounters) + { + OutputFormat("%s: ", entry.mName); + OutputUint64Line(counters->*entry.mValuePtr); + } + } + /** + * @cli srp client counters reset + * @par api_copy + * #otSrpClientResetCounters + */ + else if ((aArgs[0] == "reset") && aArgs[1].IsEmpty()) + { + otSrpClientResetCounters(GetInstancePtr()); + } + else + { + error = OT_ERROR_INVALID_ARGS; + } + + return error; +} +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + template <> otError SrpClient::Process(Arg aArgs[]) { otError error = OT_ERROR_NONE; @@ -979,9 +1082,15 @@ otError SrpClient::Process(Arg aArgs[]) #define CmdEntry(aCommandString) {aCommandString, &SrpClient::Process} static constexpr Command kCommands[] = { - CmdEntry("autostart"), CmdEntry("callback"), CmdEntry("host"), CmdEntry("keyleaseinterval"), - CmdEntry("leaseinterval"), CmdEntry("server"), CmdEntry("service"), CmdEntry("start"), - CmdEntry("state"), CmdEntry("stop"), CmdEntry("ttl"), + CmdEntry("autostart"), CmdEntry("callback"), +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + CmdEntry("counters"), +#endif + CmdEntry("host"), CmdEntry("keyleaseinterval"), + CmdEntry("leaseinterval"), CmdEntry("server"), + CmdEntry("service"), CmdEntry("start"), + CmdEntry("state"), CmdEntry("stop"), + CmdEntry("ttl"), }; static_assert(BinarySearch::IsSorted(kCommands), "kCommands is not sorted"); diff --git a/src/core/api/srp_client_api.cpp b/src/core/api/srp_client_api.cpp index 1582dd86d..f03d0bad3 100644 --- a/src/core/api/srp_client_api.cpp +++ b/src/core/api/srp_client_api.cpp @@ -183,4 +183,13 @@ bool otSrpClientIsServiceKeyRecordEnabled(otInstance *aInstance) } #endif +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE +const otSrpClientCounters *otSrpClientGetCounters(otInstance *aInstance) +{ + return &AsCoreType(aInstance).Get().GetCounters(); +} + +void otSrpClientResetCounters(otInstance *aInstance) { AsCoreType(aInstance).Get().ResetCounters(); } +#endif + #endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE diff --git a/src/core/config/srp_client.h b/src/core/config/srp_client.h index 34423dc11..02bd15e00 100644 --- a/src/core/config/srp_client.h +++ b/src/core/config/srp_client.h @@ -54,6 +54,20 @@ #define OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + * + * Define to 1 to enable SRP Client counters APIs (`otSrpClientGetCounters` and `otSrpClientResetCounters`). + * + * Defaults to enabled when both `OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE` and `OPENTHREAD_CONFIG_UPTIME_ENABLE` are + * enabled, and disabled otherwise. Requires `OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE` and + * `OPENTHREAD_CONFIG_UPTIME_ENABLE`. + */ +#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE +#define OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE \ + (OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE && OPENTHREAD_CONFIG_UPTIME_ENABLE) +#endif + /** * @def OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE * diff --git a/src/core/net/srp_client.cpp b/src/core/net/srp_client.cpp index 04afd3199..be321a8e8 100644 --- a/src/core/net/srp_client.cpp +++ b/src/core/net/srp_client.cpp @@ -260,10 +260,14 @@ const char *Client::TxJitter::ReasonToString(Reason aReason) #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE -Client::AutoStart::AutoStart(void) +Client::AutoStart::AutoStart(Instance &aInstance) + : InstanceLocator(aInstance) + , mState(kDefaultMode ? kFirstTimeSelecting : kDisabled) + , mAnycastSeqNum(0) +#if OPENTHREAD_CONFIG_SRP_CLIENT_SWITCH_SERVER_ON_FAILURE + , mTimeoutFailureCount(0) +#endif { - Clear(); - mState = kDefaultMode ? kFirstTimeSelecting : kDisabled; } bool Client::AutoStart::HasSelectedServer(void) const @@ -291,6 +295,9 @@ void Client::AutoStart::SetState(State aState) { if (mState != aState) { +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + Get().UpdateTimeCounters(); +#endif LogInfo("AutoStartState %s -> %s", StateToString(mState), StateToString(aState)); mState = aState; } @@ -348,6 +355,7 @@ Client::Client(Instance &aInstance) , mTimer(aInstance) #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE , mGuardTimer(aInstance) + , mAutoStart(aInstance) #endif { // The `Client` implementation uses different constant array of @@ -370,6 +378,11 @@ Client::Client(Instance &aInstance) }; mHostInfo.Init(); + +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + ClearAllBytes(mCounters); + mLastUpdatedTimestamp = Get().GetUptime(); +#endif } Error Client::Start(const Ip6::SockAddr &aServerSockAddr, Requester aRequester) @@ -735,6 +748,10 @@ Error Client::UpdateHostInfoStateOnAddressChange(void) VerifyOrExit((mHostInfo.GetState() != kToRemove) && (mHostInfo.GetState() != kRemoving), error = kErrorInvalidState); +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mHostAddressChanges++; +#endif + if (mHostInfo.GetState() == kRemoved) { mHostInfo.SetState(kToAdd); @@ -757,6 +774,10 @@ Error Client::AddService(Service &aService) SuccessOrExit(error = aService.Init()); mServices.Push(aService); +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mServiceAdds++; +#endif + aService.SetState(kToAdd); UpdateState(); @@ -771,6 +792,10 @@ Error Client::RemoveService(Service &aService) VerifyOrExit(mServices.Contains(aService), error = kErrorNotFound); +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mServiceRemoves++; +#endif + UpdateServiceStateToRemove(aService); UpdateState(); @@ -791,6 +816,11 @@ Error Client::ClearService(Service &aService) Error error; SuccessOrExit(error = mServices.Remove(aService)); + +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mServiceClears++; +#endif + aService.SetNext(nullptr); aService.SetState(kRemoved); UpdateState(); @@ -830,6 +860,10 @@ Error Client::RemoveHostAndServices(bool aShouldRemoveKeyLease, bool aSendUnregT ExitNow(); } +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mHostAndServicesRemoves++; +#endif + mHostInfo.SetState(kToRemove); UpdateState(); @@ -841,6 +875,10 @@ void Client::ClearHostAndServices(void) { LogInfo("Clear host & services"); +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mHostAndServicesClears++; +#endif + switch (GetState()) { case kStateStopped: @@ -867,6 +905,11 @@ void Client::SetState(State aState) VerifyOrExit(aState != mState); LogInfo("State %s -> %s", StateToString(mState), StateToString(aState)); + +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + UpdateTimeCounters(); +#endif + mState = aState; switch (mState) @@ -892,6 +935,45 @@ exit: return; } +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + +const Client::Counters &Client::GetCounters(void) +{ + UpdateTimeCounters(); + return mCounters; +} + +void Client::ResetCounters(void) +{ + ClearAllBytes(mCounters); + mLastUpdatedTimestamp = Get().GetUptime(); +} + +void Client::UpdateTimeCounters(void) +{ + UptimeMsec now = Get().GetUptime(); + UptimeMsec duration = now - mLastUpdatedTimestamp; + + mLastUpdatedTimestamp = now; + mCounters.mTrackedTime += duration; + + if (mState == kStateUpdated) + { + mCounters.mRegisteredTime += duration; + + if (GetServerAddress().GetAddress().GetIid().IsAnycastServiceLocator()) + { + mCounters.mAnycastAvailableTime += duration; + } + else + { + mCounters.mUnicastAvailableTime += duration; + } + } +} + +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + bool Client::ChangeHostAndServiceStates(const ItemState *aNewStates, ServiceStateChangeMode aMode) { bool anyChanged; @@ -976,6 +1058,9 @@ void Client::SendUpdate(void) MsgInfo info; uint32_t length; bool anyChanged; +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + uint16_t txPayloadLength; +#endif info.mMessage.Reset(mSocket.NewMessage()); VerifyOrExit(info.mMessage != nullptr, error = kErrorNoBufs); @@ -1012,12 +1097,24 @@ void Client::SendUpdate(void) if (anyChanged) { SelectNewMessageId(); +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mUpdateAttempts++; +#endif } SuccessOrExit(error = UpdateIdAndSignatureInUpdateMessage(info)); +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + txPayloadLength = info.mMessage->GetLength(); +#endif + SuccessOrExit(error = mSocket.SendTo(*info.mMessage, Ip6::MessageInfo())); +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mTxUpdates++; + mCounters.mTxTotalBytes += txPayloadLength; +#endif + // Ownership of the message is transferred to the socket upon a // successful `SendTo()` call. @@ -1820,6 +1917,21 @@ void Client::ProcessResponse(Message &aMessage) { LogInfo("Server rejected %s code:%d", ErrorToString(error), header.GetResponseCode()); +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + switch (error) + { + case kErrorDuplicated: + mCounters.mRejectedDuplicate++; + break; + case kErrorSecurity: + mCounters.mRejectedSecurity++; + break; + default: + mCounters.mRejectedOther++; + break; + } +#endif + if (mHostInfo.GetState() == kAdding) { // Since server rejected the update message, we go back to @@ -1929,6 +2041,10 @@ void Client::ProcessResponse(Message &aMessage) ChangeHostAndServiceStates(kNewStateOnUpdateDone, kForServicesAppendedInMessage); +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mSuccess++; +#endif + HandleUpdateDone(); UpdateState(); @@ -2197,6 +2313,9 @@ void Client::HandleTimer(void) case kStateUpdating: LogRetryWaitInterval(); LogInfo("Timed out, no response"); +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mTimeouts++; +#endif GrowRetryWaitInterval(); SetState(kStateToUpdate); InvokeCallback(kErrorResponseTimeout); @@ -2394,6 +2513,10 @@ void Client::ProcessAutoStart(void) break; } +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + mCounters.mServerChanges++; +#endif + IgnoreError(Start(serverSockAddr, kRequesterAuto)); exit: diff --git a/src/core/net/srp_client.hpp b/src/core/net/srp_client.hpp index d5cd20753..cd6a2fdf6 100644 --- a/src/core/net/srp_client.hpp +++ b/src/core/net/srp_client.hpp @@ -47,6 +47,7 @@ #include "common/numeric_limits.hpp" #include "common/owned_ptr.hpp" #include "common/timer.hpp" +#include "common/uptime.hpp" #include "crypto/ecdsa.hpp" #include "net/dns_types.hpp" #include "net/ip6.hpp" @@ -66,6 +67,14 @@ namespace Srp { #error "SRP Client feature requires ECDSA support (OPENTHREAD_CONFIG_ECDSA_ENABLE)." #endif +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE && !OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE +#error "OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE requires OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE." +#endif + +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE && !OPENTHREAD_CONFIG_UPTIME_ENABLE +#error "OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE requires OPENTHREAD_CONFIG_UPTIME_ENABLE." +#endif + /** * Implements SRP client. */ @@ -757,6 +766,25 @@ public: #endif // OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + /** + * Represents the SRP client counters (matches `otSrpClientCounters`). + */ + typedef otSrpClientCounters Counters; + + /** + * Gets the SRP client counters. + * + * @returns A reference to the SRP client counters. + */ + const Counters &GetCounters(void); + + /** + * Resets the SRP client counters. + */ + void ResetCounters(void); +#endif + private: // Number of fast data polls after SRP Update tx (11x 188ms = ~2 seconds) static constexpr uint8_t kFastPollsAfterUpdateTx = 11; @@ -960,7 +988,7 @@ private: }; #if OPENTHREAD_CONFIG_SRP_CLIENT_AUTO_START_API_ENABLE - class AutoStart : public Clearable + class AutoStart : public InstanceLocator { public: enum State : uint8_t @@ -973,7 +1001,7 @@ private: kSelectedUnicast, // Has selected a unicast entry (address in server data). }; - AutoStart(void); + explicit AutoStart(Instance &aInstance); bool HasSelectedServer(void) const; State GetState(void) const { return mState; } void SetState(State aState); @@ -1077,6 +1105,10 @@ private: #endif #endif +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + void UpdateTimeCounters(void); +#endif + #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) static const char *StateToString(State aState); void LogRetryWaitInterval(void) const; @@ -1127,6 +1159,10 @@ private: GuardTimer mGuardTimer; AutoStart mAutoStart; #endif +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + Counters mCounters; + UptimeMsec mLastUpdatedTimestamp; +#endif }; } // namespace Srp diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index f34033e04..21b2833d9 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -448,6 +448,7 @@ ot_nexus_test(router_reboot_multiple_link_request "core;nexus") ot_nexus_test(service "core;nexus") ot_nexus_test(srp_auto_start "core;nexus") ot_nexus_test(srp_client_change_lease "core;nexus") +ot_nexus_test(srp_client_counters "core;nexus") ot_nexus_test(srp_client_remove_host "core;nexus") ot_nexus_test(srp_client_save_server_info "core;nexus") ot_nexus_test(srp_lease "core;nexus") diff --git a/tests/nexus/test_srp_client_counters.cpp b/tests/nexus/test_srp_client_counters.cpp new file mode 100644 index 000000000..c8e61c82c --- /dev/null +++ b/tests/nexus/test_srp_client_counters.cpp @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2026, 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. + */ + +#include + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +#if OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + +namespace ot { +namespace Nexus { + +static constexpr uint32_t kFormNetworkTime = 13 * 1000; +static constexpr uint32_t kJoinNetworkTime = 10 * 1000; +static constexpr uint32_t kRegistrationTime = 5 * 1000; + +static constexpr uint32_t kLeaseTime = 60; +static constexpr uint32_t kKeyLeaseTime = 240; + +static const char kSrpServiceType[] = "_ipps._tcp"; +static const char kSrpInstanceName[] = "my-service"; +static const char kSrpInstanceName2[] = "my-service-2"; +static const char kSrpHostName[] = "my-host"; +static const char kSrpHostAddress[] = "2001::1"; +static constexpr uint16_t kSrpServicePort = 12345; +static constexpr uint16_t kSrpServicePort2 = 12346; + +void TestSrpClientCounters(void) +{ + Core nexus; + Ip6::Address srpHostAddress; + Srp::Client::Service srpService; + Srp::Client::Service srpService2; + + Node &server = nexus.CreateNode(); + Node &client = nexus.CreateNode(); + + server.SetName("SRP_SERVER"); + client.SetName("SRP_CLIENT"); + + SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevelNote)); + + Log("Step 0: Form network and bring up server."); + + server.Form(); + nexus.AdvanceTime(kFormNetworkTime); + + { + Srp::Server::LeaseConfig leaseConfig; + leaseConfig.mMinLease = kLeaseTime; + leaseConfig.mMaxLease = kLeaseTime; + leaseConfig.mMinKeyLease = kKeyLeaseTime; + leaseConfig.mMaxKeyLease = kKeyLeaseTime; + SuccessOrQuit(server.Get().SetLeaseConfig(leaseConfig)); + } + server.Get().SetEnabled(true); + nexus.AdvanceTime(5 * 1000); + + client.Join(server, Node::kAsFed); + nexus.AdvanceTime(kJoinNetworkTime); + + Log("Step 1: Verify counters start at zero."); + { + const otSrpClientCounters &counters = client.Get().GetCounters(); + + VerifyOrQuit(counters.mTxUpdates == 0); + VerifyOrQuit(counters.mUpdateAttempts == 0); + VerifyOrQuit(counters.mSuccess == 0); + VerifyOrQuit(counters.mRejectedDuplicate == 0); + VerifyOrQuit(counters.mRejectedSecurity == 0); + VerifyOrQuit(counters.mRejectedOther == 0); + VerifyOrQuit(counters.mTimeouts == 0); + VerifyOrQuit(counters.mHostAddressChanges == 0); + VerifyOrQuit(counters.mServerChanges == 0); + VerifyOrQuit(counters.mServiceAdds == 0); + VerifyOrQuit(counters.mServiceRemoves == 0); + VerifyOrQuit(counters.mServiceClears == 0); + VerifyOrQuit(counters.mHostAndServicesRemoves == 0); + VerifyOrQuit(counters.mHostAndServicesClears == 0); + VerifyOrQuit(counters.mTxTotalBytes == 0); + VerifyOrQuit(counters.mRegisteredTime == 0); + VerifyOrQuit(counters.mAnycastAvailableTime == 0); + VerifyOrQuit(counters.mUnicastAvailableTime == 0); + } + + Log("Step 2: Register a service and verify Tx/Attempts/Success/ServerChanges/UnicastAvailable counters advance."); + + client.Get().EnableAutoStartMode(nullptr, nullptr); + SuccessOrQuit(client.Get().SetHostName(kSrpHostName)); + SuccessOrQuit(srpHostAddress.FromString(kSrpHostAddress)); + SuccessOrQuit(client.Get().SetHostAddresses(&srpHostAddress, 1)); + + ClearAllBytes(srpService); + srpService.mName = kSrpServiceType; + srpService.mInstanceName = kSrpInstanceName; + srpService.mPort = kSrpServicePort; + SuccessOrQuit(client.Get().AddService(srpService)); + + nexus.AdvanceTime(kRegistrationTime); + + VerifyOrQuit(client.Get().GetHostInfo().GetState() == Srp::Client::kRegistered); + + { + const otSrpClientCounters &counters = client.Get().GetCounters(); + + VerifyOrQuit(counters.mTxUpdates >= 1); + VerifyOrQuit(counters.mUpdateAttempts >= 1); + VerifyOrQuit(counters.mSuccess >= 1); + VerifyOrQuit(counters.mServerChanges >= 1); + VerifyOrQuit(counters.mServiceAdds == 1); + VerifyOrQuit(counters.mHostAddressChanges >= 1); + VerifyOrQuit(counters.mTxTotalBytes > 0); + VerifyOrQuit(counters.mTrackedTime > 0); + VerifyOrQuit(counters.mRegisteredTime <= counters.mTrackedTime); + VerifyOrQuit(counters.mUnicastAvailableTime > 0); + VerifyOrQuit(counters.mUnicastAvailableTime <= counters.mRegisteredTime); + VerifyOrQuit(counters.mAnycastAvailableTime == 0); + + // Wire-level transmissions are at least as numerous as logical transactions. + VerifyOrQuit(counters.mTxUpdates >= counters.mUpdateAttempts); + + // Every completed transaction was attempted. + VerifyOrQuit(counters.mUpdateAttempts >= counters.mSuccess + counters.mRejectedDuplicate + + counters.mRejectedSecurity + counters.mRejectedOther); + } + + Log("Step 3: Wait for a refresh cycle and verify counters keep advancing."); + { + const otSrpClientCounters &before = client.Get().GetCounters(); + uint32_t txBefore = before.mTxUpdates; + uint32_t attemptsBefore = before.mUpdateAttempts; + uint32_t successBefore = before.mSuccess; + uint64_t unicastAvailableBefore = before.mUnicastAvailableTime; + + nexus.AdvanceTime(kLeaseTime * 1000); + + const otSrpClientCounters &after = client.Get().GetCounters(); + + VerifyOrQuit(after.mTxUpdates > txBefore); + VerifyOrQuit(after.mUpdateAttempts > attemptsBefore); + VerifyOrQuit(after.mSuccess > successBefore); + VerifyOrQuit(after.mRegisteredTime > 0); + VerifyOrQuit(after.mUnicastAvailableTime > unicastAvailableBefore); + } + + Log("Step 4: Add a second service, remove the first, clear the second; verify Service{Adds,Removes,Clears}."); + { + ClearAllBytes(srpService2); + srpService2.mName = kSrpServiceType; + srpService2.mInstanceName = kSrpInstanceName2; + srpService2.mPort = kSrpServicePort2; + SuccessOrQuit(client.Get().AddService(srpService2)); + + nexus.AdvanceTime(kRegistrationTime); + + VerifyOrQuit(client.Get().GetCounters().mServiceAdds == 2); + + SuccessOrQuit(client.Get().RemoveService(srpService)); + + nexus.AdvanceTime(kRegistrationTime); + + VerifyOrQuit(client.Get().GetCounters().mServiceRemoves == 1); + + SuccessOrQuit(client.Get().ClearService(srpService2)); + + VerifyOrQuit(client.Get().GetCounters().mServiceClears == 1); + } + + Log("Step 5: Remove host & services, then clear; verify HostAndServices{Removes,Clears}."); + { + SuccessOrQuit(client.Get().RemoveHostAndServices(/* aShouldRemoveKeyLease */ false, + /* aSendUnregToServer */ false)); + + nexus.AdvanceTime(kRegistrationTime); + + VerifyOrQuit(client.Get().GetCounters().mHostAndServicesRemoves == 1); + + client.Get().ClearHostAndServices(); + + VerifyOrQuit(client.Get().GetCounters().mHostAndServicesClears == 1); + } + + Log("Step 6: Reset counters and verify all event counters return to zero."); + { + client.Get().ResetCounters(); + + const otSrpClientCounters &counters = client.Get().GetCounters(); + + VerifyOrQuit(counters.mTxUpdates == 0); + VerifyOrQuit(counters.mUpdateAttempts == 0); + VerifyOrQuit(counters.mSuccess == 0); + VerifyOrQuit(counters.mRejectedDuplicate == 0); + VerifyOrQuit(counters.mRejectedSecurity == 0); + VerifyOrQuit(counters.mRejectedOther == 0); + VerifyOrQuit(counters.mTimeouts == 0); + VerifyOrQuit(counters.mHostAddressChanges == 0); + VerifyOrQuit(counters.mServerChanges == 0); + VerifyOrQuit(counters.mServiceAdds == 0); + VerifyOrQuit(counters.mServiceRemoves == 0); + VerifyOrQuit(counters.mServiceClears == 0); + VerifyOrQuit(counters.mHostAndServicesRemoves == 0); + VerifyOrQuit(counters.mHostAndServicesClears == 0); + VerifyOrQuit(counters.mTxTotalBytes == 0); + VerifyOrQuit(counters.mRegisteredTime == 0); + VerifyOrQuit(counters.mAnycastAvailableTime == 0); + VerifyOrQuit(counters.mUnicastAvailableTime == 0); + VerifyOrQuit(counters.mTrackedTime == 0); + } + + Log("Step 7: Verify time tracking restarts cleanly after reset."); + { + nexus.AdvanceTime(kRegistrationTime); + + const otSrpClientCounters &counters = client.Get().GetCounters(); + + VerifyOrQuit(counters.mTrackedTime > 0); + VerifyOrQuit(counters.mRegisteredTime <= counters.mTrackedTime); + } + + // Note: mTimeouts, mRejected{Duplicate,Security,Other}, and mAnycastAvailableTime are + // not exercised by this nexus test — those paths require packet manipulation or an + // anycast-published SRP server. Their increment sites are simple single-line bumps + // verified by code review. +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestSrpClientCounters(); + printf("All tests passed\n"); + return 0; +} + +#else // OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE + +int main(void) +{ + printf("OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE is disabled, skipping test\n"); + return 0; +} + +#endif // OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE diff --git a/tests/toranj/openthread-core-toranj-config-posix.h b/tests/toranj/openthread-core-toranj-config-posix.h index c73eb4e6e..81916cf1f 100644 --- a/tests/toranj/openthread-core-toranj-config-posix.h +++ b/tests/toranj/openthread-core-toranj-config-posix.h @@ -99,4 +99,6 @@ #define OPENTHREAD_CONFIG_HISTORY_TRACKER_CLIENT_ENABLE 0 +#define OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE 0 + #endif // OT_TORANJ_OPENTHREAD_CORE_TORANJ_CONFIG_POSIX_H_ diff --git a/tests/toranj/openthread-core-toranj-config-simulation.h b/tests/toranj/openthread-core-toranj-config-simulation.h index 5ec453d02..95dd1ccf6 100644 --- a/tests/toranj/openthread-core-toranj-config-simulation.h +++ b/tests/toranj/openthread-core-toranj-config-simulation.h @@ -51,6 +51,8 @@ #define OPENTHREAD_CONFIG_SRP_SERVER_FAST_START_MODE_ENABLE 1 +#define OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE 1 + #define OPENTHREAD_CONFIG_COAP_API_ENABLE 1 #define OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE 1