Files
Abtin Keshavarzian 4de7bc578e [random] introduce template-based NonCrypto random APIs (#13142)
This commit introduces a new set of template-based APIs for
non-cryptographic random number generation in the `Random::NonCrypto`
namespace. These new methods provide a cleaner, type-safe, and more
robust interface compared to the previous methods.

Key additions:
- `Generate<UintType>()`: Returns a random value of the given
  unsigned integer type (`uint8_t`, `uint16_t`, or `uint32_t`).
- `GenerateUpToExcluding<UintType>(aMax)`: Returns a random value in
  the range `[0, aMax)`.
- `GenerateFromMinUpToExcluding<UintType>(aMin, aMax)`: Returns a
  random value in the range `[aMin, aMax)`.
- `GenerateInClosedRange<UintType>(aMin, aMax)`: Returns a random
  value in the closed range `[aMin, aMax]`.

The introduction of `GenerateInClosedRange` is an improvement as it
safely handles ranges up to the maximum value of the integer type
(e.g., `0xffff`) without the risk of overflow.

All call sites across the OpenThread core stack and tests have been
updated to adopt these new APIs. The public `otRandomNonCrypto`
functions are also updated to leverage the new internal methods.

Doxygen documentation is added for all new template methods,
detailing their behavior, including edge cases where the upper bound
is smaller than or equal to the lower bound.
2026-05-25 19:39:59 -07:00

397 lines
13 KiB
C++

/*
* 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 <string.h>
#include "platform/nexus_core.hpp"
#include "platform/nexus_node.hpp"
namespace ot {
namespace Nexus {
static const uint16_t kNumNodes[] = {1, 5, 10}; // Number of nodes to use in different test runs
constexpr uint16_t kTestIterationsPerNumNodes = 1; // Number of iterations to run per num-node
enum State : uint8_t
{
kNotRegistered, // No SRP registration yet
kRegistered, // SRP service registered
kUnregistered, // Unregistered the previously registered service (retaining name)
kRemoved, // Fully removed host and service (not retaining key-lease)
kDisconnected, // Disconnected (checking if the server correctly expires the previously registered service)
kValidated, // Final state of all nodes indicating validated server behavior
};
constexpr uint16_t kMaxNodes = 40;
// Test advances time in `kStepInterval`
constexpr uint32_t kStepInterval = 120 * TimeMilli::kOneSecondInMsec;
constexpr uint16_t kStepJitter = 10 * TimeMilli::kOneSecondInMsec;
constexpr uint32_t kLeaseInterval = 7200 * TimeMilli::kOneSecondInMsec;
// All probabilities are percentage times 10
constexpr uint16_t kRegisterProbability = 8;
constexpr uint16_t kUpdateRegProbability = 8;
constexpr uint16_t kUnregisterProbability = 1;
constexpr uint16_t kRemoveProbability = 1;
constexpr uint16_t kDisconnectProbability = 4;
static const char *StateToString(State aState)
{
#define StateMapList(_) \
_(kNotRegistered, "NotRegistered") \
_(kRegistered, "Registered") \
_(kUnregistered, "Unregistered") \
_(kRemoved, "Removed") \
_(kDisconnected, "Disconnected") \
_(kValidated, "Validated")
DefineEnumStringArray(StateMapList);
return kStrings[aState];
}
struct NodeInfo
{
void SetState(State aState, uint16_t aNodeId)
{
Log("Node %u: %s", aNodeId, StateToString(aState));
mState = aState;
mUpdateTime = TimerMilli::GetNow();
}
State mState;
TimeMilli mUpdateTime;
Srp::Client::Service mService;
const char *mSubTypeLabels[5];
};
NodeInfo sInfo[kMaxNodes];
static void RegisterSrp(Node &aNode)
{
NodeInfo *info;
Srp::Client::Service *service;
VerifyOrQuit(aNode.GetId() < kMaxNodes);
info = &sInfo[aNode.GetId()];
service = &info->mService;
aNode.Get<Srp::Client>().EnableAutoStartMode(nullptr, nullptr);
SuccessOrQuit(aNode.Get<Srp::Client>().EnableAutoHostAddress());
SuccessOrQuit(aNode.Get<Srp::Client>().SetHostName(aNode.GetName()));
ClearAllBytes(*service);
service->mName = "_test._udp";
service->mInstanceName = aNode.GetName();
service->mSubTypeLabels = nullptr;
service->mTxtEntries = nullptr;
service->mNumTxtEntries = 0;
service->mPort = 1000 + aNode.GetId();
SuccessOrQuit(aNode.Get<Srp::Client>().AddService(*service));
}
static void UpdateSrpRegistration(Node &aNode)
{
NodeInfo *info;
Srp::Client::Service *service;
VerifyOrQuit(aNode.GetId() < kMaxNodes);
aNode.Get<Srp::Client>().ClearHostAndServices();
info = &sInfo[aNode.GetId()];
service = &info->mService;
SuccessOrQuit(aNode.Get<Srp::Client>().EnableAutoHostAddress());
SuccessOrQuit(aNode.Get<Srp::Client>().SetHostName(aNode.GetName()));
ClearAllBytes(*service);
service->mName = "_test._udp";
service->mInstanceName = aNode.GetName();
service->mSubTypeLabels = nullptr;
service->mTxtEntries = nullptr;
service->mNumTxtEntries = 0;
service->mPort = Random::NonCrypto::GenerateFromMinUpToExcluding<uint16_t>(0x100, 0xff00);
service->mSubTypeLabels = &info->mSubTypeLabels[Random::NonCrypto::GenerateUpToExcluding<uint8_t>(4)];
SuccessOrQuit(aNode.Get<Srp::Client>().AddService(*service));
}
static bool ShouldPerform(uint16_t aProbability)
{
// Uses the given probability to randomly decide whether a certain action should be performed.
return Random::NonCrypto::GenerateUpToExcluding<uint16_t>(1000) < aProbability;
}
static const Srp::Server::Host *FindHost(Node &aServer, const char *aName)
{
// Find a host on `aServer` SRP server matching `aName`
static constexpr uint16_t kNameStringSize = 400;
const Srp::Server::Host *host = nullptr;
String<kNameStringSize> fullName;
fullName.Append("%s.default.service.arpa.", aName);
while (true)
{
host = aServer.Get<Srp::Server>().GetNextHost(host);
if (host == nullptr)
{
break;
}
if (StringMatch(host->GetFullName(), fullName.AsCString()))
{
break;
}
}
return host;
}
void LogSrpServer(Node &aServer)
{
const Srp::Server::Host *host = nullptr;
Log("- - - - - -");
while (true)
{
host = aServer.Get<Srp::Server>().GetNextHost(host);
if (host == nullptr)
{
break;
}
Log("%s, is-deleted:%s", host->GetFullName(), ToYesNo(host->IsDeleted()));
}
}
void TestSrpLease(uint16_t aNumNodes)
{
Core nexus;
Node *nodes[kMaxNodes];
Node *leader;
VerifyOrQuit(aNumNodes + 1 <= kMaxNodes);
Log("----------------------------------------------------------------------------------------------------------");
Log("Testing with %u nodes", aNumNodes);
leader = &nexus.CreateNode();
leader->Form();
nexus.AdvanceTime(100 * Time::kOneSecondInMsec);
VerifyOrQuit(leader->Get<Mle::Mle>().IsLeader());
leader->Get<Srp::Server>().SetEnabled(true);
nexus.AdvanceTime(30 * Time::kOneSecondInMsec);
VerifyOrQuit(leader->Get<Srp::Server>().GetState() == Srp::Server::kStateRunning);
nodes[0] = leader;
for (uint16_t i = 1; i <= aNumNodes; i++)
{
sInfo[i].mState = kNotRegistered;
sInfo[i].mSubTypeLabels[0] = "_s1";
sInfo[i].mSubTypeLabels[1] = "_xvab";
sInfo[i].mSubTypeLabels[2] = "_I12345678";
sInfo[i].mSubTypeLabels[3] = "_cm";
sInfo[i].mSubTypeLabels[4] = nullptr;
nodes[i] = &nexus.CreateNode();
nodes[i]->SetName("client", i);
nodes[i]->Join(*leader, Node::kAsFed);
nexus.AdvanceTime(100);
}
nexus.AdvanceTime(10 * Time::kOneSecondInMsec);
for (uint16_t i = 1; i <= aNumNodes; i++)
{
VerifyOrQuit(nodes[i]->Get<Mle::Mle>().IsChild());
}
while (true)
{
uint16_t nodesValidated = 0;
nexus.AdvanceTime(Random::NonCrypto::AddJitter(kStepInterval, kStepJitter));
// LogSrpServer(*leader);
for (uint16_t i = 1; i <= aNumNodes; i++)
{
NodeInfo &info = sInfo[i];
const Srp::Server::Host *host;
switch (info.mState)
{
case kNotRegistered:
if (ShouldPerform(kRegisterProbability))
{
RegisterSrp(*nodes[i]);
nexus.AdvanceTime(1000);
VerifyOrQuit(nodes[i]->Get<Srp::Client>().GetHostInfo().GetState() == Srp::Client::kRegistered);
info.SetState(kRegistered, i);
}
break;
case kRegistered:
host = FindHost(*leader, nodes[i]->GetName());
VerifyOrQuit(host != nullptr);
VerifyOrQuit(!host->IsDeleted());
VerifyOrQuit(!host->GetServices().IsEmpty());
VerifyOrQuit(!host->GetServices().GetHead()->IsDeleted());
if (ShouldPerform(kUpdateRegProbability))
{
UpdateSrpRegistration(*nodes[i]);
nexus.AdvanceTime(1000);
VerifyOrQuit(nodes[i]->Get<Srp::Client>().GetHostInfo().GetState() == Srp::Client::kRegistered);
Log("Node %u: Updated SRP", i);
}
else if (ShouldPerform(kDisconnectProbability))
{
nodes[i]->Reset();
info.SetState(kDisconnected, i);
}
else if (ShouldPerform(kUnregisterProbability))
{
SuccessOrQuit(nodes[i]->Get<Srp::Client>().RemoveHostAndServices(/* aRemoveKeyLease */ false));
info.SetState(kUnregistered, i);
}
else if (ShouldPerform(kRemoveProbability))
{
SuccessOrQuit(nodes[i]->Get<Srp::Client>().RemoveHostAndServices(/* aRemoveKeyLease */ true));
info.SetState(kRemoved, i);
}
break;
case kUnregistered:
// If the client unregistered (retaining the key-lease), the `host`
// on the server must be marked as deleted.
host = FindHost(*leader, nodes[i]->GetName());
VerifyOrQuit(host != nullptr);
VerifyOrQuit(host->IsDeleted());
if (!host->GetServices().IsEmpty())
{
VerifyOrQuit(host->GetServices().GetHead()->IsDeleted());
}
info.SetState(kValidated, i);
break;
case kRemoved:
// If the client fully removes the registration, the `host` on
// the server must be fully removed.
host = FindHost(*leader, nodes[i]->GetName());
VerifyOrQuit(host == nullptr);
info.SetState(kValidated, i);
break;
case kDisconnected:
// If the client disconnects without changing its registration,
// we ensure the `host` on the server is marked as deleted once
// its lease time expires.
host = FindHost(*leader, nodes[i]->GetName());
if (host != nullptr)
{
if (host->IsDeleted())
{
VerifyOrQuit(!host->GetServices().IsEmpty());
VerifyOrQuit(host->GetServices().GetHead()->IsDeleted());
info.SetState(kValidated, i);
}
else
{
if (info.mUpdateTime + kLeaseInterval < TimerMilli::GetNow())
{
Log("Node %u is still on server beyond its lease time", i);
VerifyOrQuit(false);
}
}
}
else
{
info.SetState(kValidated, i);
}
break;
case kValidated:
nodesValidated++;
}
}
if (nodesValidated == aNumNodes)
{
Log("Test passed successfully");
break;
}
}
}
} // namespace Nexus
} // namespace ot
int main(void)
{
for (uint16_t numNodes : ot::Nexus::kNumNodes)
{
for (uint16_t iter = 0; iter < ot::Nexus::kTestIterationsPerNumNodes; iter++)
{
ot::Nexus::TestSrpLease(numNodes);
}
}
printf("All tests passed\n");
return 0;
}