[srp-client] add counters for performance tracking (#13137)

Adds OPENTHREAD_CONFIG_SRP_CLIENT_COUNTERS_ENABLE config, APIs and a
CLI subcommand to expose SRP client metrics for on-device
diagnosis. Tracks wire-level (Tx, bytes, timeouts) and
transaction-level (attempts, success, RCODE-bucketed rejections)
outcomes, registration drivers (host-address changes, auto-start
server changes), and time accumulators for registered /
anycast-available / unicast-available state. Defaults to enabled when
the SRP client is enabled, so behavior is unchanged for existing
builds.
This commit is contained in:
sarveshkumarv3
2026-08-17 17:31:02 -07:00
committed by GitHub
parent 635772848b
commit 5cc20e1577
12 changed files with 662 additions and 9 deletions
+1 -1
View File
@@ -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
+49
View File
@@ -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);
/**
* @}
*/
+38
View File
@@ -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`
+112 -3
View File
@@ -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<Cmd("counters")>(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<Cmd("host")>(Arg aArgs[])
{
otError error = OT_ERROR_NONE;
@@ -979,9 +1082,15 @@ otError SrpClient::Process(Arg aArgs[])
#define CmdEntry(aCommandString) {aCommandString, &SrpClient::Process<Cmd(aCommandString)>}
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");
+9
View File
@@ -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<Srp::Client>().GetCounters();
}
void otSrpClientResetCounters(otInstance *aInstance) { AsCoreType(aInstance).Get<Srp::Client>().ResetCounters(); }
#endif
#endif // OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
+14
View File
@@ -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
*
+126 -3
View File
@@ -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<Client>().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<UptimeTracker>().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<UptimeTracker>().GetUptime();
}
void Client::UpdateTimeCounters(void)
{
UptimeMsec now = Get<UptimeTracker>().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:
+38 -2
View File
@@ -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<AutoStart>
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
+1
View File
@@ -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")
+270
View File
@@ -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 <stdio.h>
#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<Srp::Server>().SetLeaseConfig(leaseConfig));
}
server.Get<Srp::Server>().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<Srp::Client>().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<Srp::Client>().EnableAutoStartMode(nullptr, nullptr);
SuccessOrQuit(client.Get<Srp::Client>().SetHostName(kSrpHostName));
SuccessOrQuit(srpHostAddress.FromString(kSrpHostAddress));
SuccessOrQuit(client.Get<Srp::Client>().SetHostAddresses(&srpHostAddress, 1));
ClearAllBytes(srpService);
srpService.mName = kSrpServiceType;
srpService.mInstanceName = kSrpInstanceName;
srpService.mPort = kSrpServicePort;
SuccessOrQuit(client.Get<Srp::Client>().AddService(srpService));
nexus.AdvanceTime(kRegistrationTime);
VerifyOrQuit(client.Get<Srp::Client>().GetHostInfo().GetState() == Srp::Client::kRegistered);
{
const otSrpClientCounters &counters = client.Get<Srp::Client>().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<Srp::Client>().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<Srp::Client>().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<Srp::Client>().AddService(srpService2));
nexus.AdvanceTime(kRegistrationTime);
VerifyOrQuit(client.Get<Srp::Client>().GetCounters().mServiceAdds == 2);
SuccessOrQuit(client.Get<Srp::Client>().RemoveService(srpService));
nexus.AdvanceTime(kRegistrationTime);
VerifyOrQuit(client.Get<Srp::Client>().GetCounters().mServiceRemoves == 1);
SuccessOrQuit(client.Get<Srp::Client>().ClearService(srpService2));
VerifyOrQuit(client.Get<Srp::Client>().GetCounters().mServiceClears == 1);
}
Log("Step 5: Remove host & services, then clear; verify HostAndServices{Removes,Clears}.");
{
SuccessOrQuit(client.Get<Srp::Client>().RemoveHostAndServices(/* aShouldRemoveKeyLease */ false,
/* aSendUnregToServer */ false));
nexus.AdvanceTime(kRegistrationTime);
VerifyOrQuit(client.Get<Srp::Client>().GetCounters().mHostAndServicesRemoves == 1);
client.Get<Srp::Client>().ClearHostAndServices();
VerifyOrQuit(client.Get<Srp::Client>().GetCounters().mHostAndServicesClears == 1);
}
Log("Step 6: Reset counters and verify all event counters return to zero.");
{
client.Get<Srp::Client>().ResetCounters();
const otSrpClientCounters &counters = client.Get<Srp::Client>().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<Srp::Client>().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
@@ -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_
@@ -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