Files
openthread/tests/nexus/test_border_admitter.cpp
T
Abtin Keshavarzian 3ce616d835 [netdiag] rename namespace NetworkDiagnostic to NetDiag (#13154)
This commit renames the `NetworkDiagnostic` namespace in `src/core/thread/`
and its related types to `NetDiag` for brevity. It updates the
corresponding filenames and header guards as well.
2026-05-26 20:19:48 -07:00

3580 lines
141 KiB
C++

/*
* Copyright (c) 2025, 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 <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "platform/nexus_core.hpp"
#include "platform/nexus_node.hpp"
namespace ot {
namespace Nexus {
using Admitter = MeshCoP::BorderAgent::Admitter;
using Manager = MeshCoP::BorderAgent::Manager;
using Commissioner = MeshCoP::Commissioner;
using Joiner = MeshCoP::Joiner;
static LogLevel kLogLevel = kLogLevelCrit;
void TestBorderAdmitterPrimeSelection(void)
{
Core nexus;
Node &leader = nexus.CreateNode();
Node &node1 = nexus.CreateNode();
Node &node2 = nexus.CreateNode();
Node &node3 = nexus.CreateNode();
Node *nodes[] = {&node1, &node2, &node3};
NetworkData::Service::Iterator netDataIter(leader.GetInstance());
uint16_t rloc16;
bool found;
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAdmitterPrimeSelection");
nexus.AdvanceTime(0);
SuccessOrQuit(Instance::SetGlobalLogLevel(kLogLevel));
// Form the topology.
leader.Form();
nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
node1.Join(leader);
node2.Join(leader);
node3.Join(leader);
nexus.AdvanceTime(10 * Time::kOneMinuteInMsec);
VerifyOrQuit(leader.Get<Mle::Mle>().IsLeader());
VerifyOrQuit(node1.Get<Mle::Mle>().IsRouter());
VerifyOrQuit(node2.Get<Mle::Mle>().IsRouter());
VerifyOrQuit(node3.Get<Mle::Mle>().IsRouter());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check Border Admitter initial state");
VerifyOrQuit(!node1.Get<Admitter>().IsEnabled());
VerifyOrQuit(!node2.Get<Admitter>().IsEnabled());
VerifyOrQuit(!node3.Get<Admitter>().IsEnabled());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Admitter role on `node1` and validate that it becomes the Prime Admitter");
node1.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(node1.Get<Admitter>().IsEnabled());
nexus.AdvanceTime(45 * Time::kOneSecondInMsec);
VerifyOrQuit(node1.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!node1.Get<Admitter>().IsActiveCommissioner());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the NetworkData contains a single Admitter Service from `node1`");
netDataIter.Reset();
SuccessOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16));
VerifyOrQuit(rloc16 == node1.Get<Mle::Mle>().GetRloc16());
VerifyOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Admitter role on `node2` & `node3` and validate that `node1` remains the Prime Admitter");
node2.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(node2.Get<Admitter>().IsEnabled());
node3.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(node3.Get<Admitter>().IsEnabled());
nexus.AdvanceTime(45 * Time::kOneSecondInMsec);
VerifyOrQuit(node1.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!node1.Get<Admitter>().IsActiveCommissioner());
VerifyOrQuit(!node2.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!node3.Get<Admitter>().IsPrimeAdmitter());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the NetworkData contains the Admitter Service from `node1`");
netDataIter.Reset();
SuccessOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16));
VerifyOrQuit(rloc16 == node1.Get<Mle::Mle>().GetRloc16());
VerifyOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable Admitter role on `node1` and check that another Prime Admitter is elected");
node1.Get<Admitter>().SetEnabled(false);
VerifyOrQuit(!node1.Get<Admitter>().IsEnabled());
VerifyOrQuit(!node1.Get<Admitter>().IsPrimeAdmitter());
nexus.AdvanceTime(75 * Time::kOneSecondInMsec);
// We use `!=` to do an "exclusive or" logic check (either node2 or node3 is prime and not both)
VerifyOrQuit(node2.Get<Admitter>().IsPrimeAdmitter() != node3.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!node2.Get<Admitter>().IsActiveCommissioner());
VerifyOrQuit(!node3.Get<Admitter>().IsActiveCommissioner());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the NetworkData contains a single Admitter Service entry");
netDataIter.Reset();
SuccessOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16));
VerifyOrQuit(rloc16 == (node2.Get<Admitter>().IsPrimeAdmitter() ? node2.Get<Mle::Mle>().GetRloc16()
: node3.Get<Mle::Mle>().GetRloc16()));
VerifyOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable Admitter role on all nodes (`node2` and `node3`)");
node2.Get<Admitter>().SetEnabled(false);
VerifyOrQuit(!node2.Get<Admitter>().IsEnabled());
VerifyOrQuit(!node2.Get<Admitter>().IsPrimeAdmitter());
node3.Get<Admitter>().SetEnabled(false);
VerifyOrQuit(!node3.Get<Admitter>().IsEnabled());
VerifyOrQuit(!node3.Get<Admitter>().IsPrimeAdmitter());
nexus.AdvanceTime(5 * Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the NetworkData contains no Admitter Service entry");
netDataIter.Reset();
VerifyOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16) == kErrorNotFound);
nexus.AdvanceTime(10 * Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Admitter role on all 3 nodes at the same time");
node1.Get<Admitter>().SetEnabled(true);
node2.Get<Admitter>().SetEnabled(true);
node3.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(node1.Get<Admitter>().IsEnabled());
VerifyOrQuit(node2.Get<Admitter>().IsEnabled());
VerifyOrQuit(node3.Get<Admitter>().IsEnabled());
nexus.AdvanceTime(75 * Time::kOneSecondInMsec);
Log("Validate that we end up with a single Prime Admitter");
found = false;
for (Node *node : nodes)
{
if (node->Get<Admitter>().IsPrimeAdmitter())
{
VerifyOrQuit(!found);
found = true;
rloc16 = node->Get<Mle::Mle>().GetRloc16();
}
VerifyOrQuit(!node->Get<Admitter>().IsActiveCommissioner());
}
VerifyOrQuit(found);
}
//---------------------------------------------------------------------------------------------------------------------
enum AdmitterState : uint8_t
{
kAdmitterUnavailable = 0,
kAdmitterReady = 1,
kAdmitterActive = 2,
kAdmitterConflictError = 3,
};
const char *AdmitterStateToString(uint8_t aState)
{
const char *str = "Unknown";
switch (aState)
{
case kAdmitterUnavailable:
str = "AdmitterUnavailable";
break;
case kAdmitterReady:
str = "AdmitterReady";
break;
case kAdmitterActive:
str = "AdmitterActive";
break;
case kAdmitterConflictError:
str = "AdmitterConflictError";
break;
default:
break;
}
return str;
}
//---------------------------------------------------------------------------------------------------------------------
struct AdmitterInfo
{
// Tracks information about Admitter from received
// messages on an Enroller.
void ParseAdmitterInfo(const Coap::Message &aResponse)
{
Error error;
error = Tlv::Find<MeshCoP::AdmitterStateTlv>(aResponse, mAdmitterState);
VerifyOrQuit(error == kErrorNone || error == kErrorNotFound);
mHasAdmitterState = (error == kErrorNone);
error = Tlv::Find<MeshCoP::CommissionerSessionIdTlv>(aResponse, mCommrSessionId);
VerifyOrQuit(error == kErrorNone || error == kErrorNotFound);
mHasCommrSessionId = (error == kErrorNone);
error = Tlv::Find<MeshCoP::JoinerUdpPortTlv>(aResponse, mJoinerUdp);
VerifyOrQuit(error == kErrorNone || error == kErrorNotFound);
mHasJoinerUdp = (error == kErrorNone);
}
bool mHasAdmitterState;
bool mHasCommrSessionId;
bool mHasJoinerUdp;
uint8_t mResponseState;
uint8_t mAdmitterState;
uint16_t mCommrSessionId;
uint16_t mJoinerUdp;
};
struct ResponseContext : public AdmitterInfo, public Clearable<ResponseContext>
{
// Tracks information in a TMF response received by an enroller.
// (populated by `HandleResponse()`).
bool mReceived;
};
void HandleResponse(void *aContext, Coap::Msg *aMsg, Error aResult)
{
OT_UNUSED_VARIABLE(aResult);
ResponseContext *responseContext;
VerifyOrQuit(aContext != nullptr);
VerifyOrQuit(aMsg != nullptr);
responseContext = static_cast<ResponseContext *>(aContext);
VerifyOrQuit(!responseContext->mReceived); // Duplicate response
responseContext->mReceived = true;
SuccessOrQuit(Tlv::Find<MeshCoP::StateTlv>(aMsg->mMessage, responseContext->mResponseState));
responseContext->ParseAdmitterInfo(aMsg->mMessage);
Log(" Received response - %s",
MeshCoP::StateTlv::StateToString(static_cast<MeshCoP::StateTlv::State>(responseContext->mResponseState)));
}
//---------------------------------------------------------------------------------------------------------------------
struct ReceiveContext
{
// Tracks information from received TMF on an Enroller send by
// Admitter.
static constexpr uint8_t kMaxEntries = 16;
void Clear(void)
{
mStateReports.Clear();
mRelayRxMsgs.DequeueAndFreeAll();
mProxyRxMsgs.DequeueAndFreeAll();
}
bool HasReceivedReportState(void) const { return !mStateReports.IsEmpty(); }
uint8_t GetLastReportedAdmitterState(void) { return mStateReports.Back()->mAdmitterState; }
Array<AdmitterInfo, kMaxEntries> mStateReports;
MessageQueue mRelayRxMsgs;
MessageQueue mProxyRxMsgs;
};
bool HandleResource(void *aContext, Uri aUri, Coap::Msg &aMsg)
{
bool didHandle = false;
ReceiveContext *recvContext;
AdmitterInfo *info;
Message *msgClone;
uint16_t joinerPort;
Ip6::InterfaceIdentifier joinerIid;
VerifyOrQuit(aContext != nullptr);
recvContext = static_cast<ReceiveContext *>(aContext);
switch (aUri)
{
case kUriEnrollerReportState:
didHandle = true;
info = recvContext->mStateReports.PushBack();
VerifyOrQuit(info != nullptr);
info->ParseAdmitterInfo(aMsg.mMessage);
VerifyOrQuit(info->mHasAdmitterState);
Log(" Received `EnrollerReportState` with state %s", AdmitterStateToString(info->mAdmitterState));
break;
case kUriRelayRx:
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerUdpPortTlv>(aMsg.mMessage, joinerPort));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(aMsg.mMessage, joinerIid));
Log(" Received `RelayRx` from joiner - port:%u iid:%s", joinerPort, joinerIid.ToString().AsCString());
msgClone = aMsg.mMessage.Clone<kNoReservedHeader>();
VerifyOrQuit(msgClone != nullptr);
recvContext->mRelayRxMsgs.Enqueue(*msgClone);
break;
case kUriProxyRx:
Log(" Received `ProxyRx`");
msgClone = aMsg.mMessage.Clone<kNoReservedHeader>();
VerifyOrQuit(msgClone != nullptr);
recvContext->mProxyRxMsgs.Enqueue(*msgClone);
break;
default:
Log(" Received unexpected URI %u", aUri);
break;
}
return didHandle;
}
//---------------------------------------------------------------------------------------------------------------------
void TestBorderAdmitterEnrollerInteraction(void)
{
static const char kEnrollerId[] = "en00";
static const char kEnrollerIdAlt[] = "en01";
static const uint8_t kEnrollerTimeoutInSec = 50;
Core nexus;
Node &admitter = nexus.CreateNode();
Node &enroller = nexus.CreateNode();
Ip6::SockAddr sockAddr;
Pskc pskc;
Admitter::Iterator iter;
Admitter::EnrollerInfo enrollerInfo;
Admitter::JoinerInfo joinerInfo;
Coap::Message *message;
uint8_t mode;
MeshCoP::SteeringData steeringData;
MeshCoP::SteeringData leaderSteeringData;
ResponseContext responseContext;
ReceiveContext recvContext;
Manager::SessionInfo *sessionInfo;
uint16_t rloc16;
uint16_t sessionId;
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAdmitterEnrollerInteraction");
nexus.AdvanceTime(0);
// Form the topology:
// - `admitter` forms its own network (acting as leader)
// - `enroller` stays disconnected.
admitter.Form();
nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Mle::Mle>().IsLeader());
SuccessOrQuit(enroller.Get<Mac::Mac>().SetPanChannel(admitter.Get<Mac::Mac>().GetPanChannel()));
enroller.Get<Mac::Mac>().SetPanId(admitter.Get<Mac::Mac>().GetPanId());
enroller.Get<ThreadNetif>().Up();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Border Admitter on `admitter`");
admitter.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(admitter.Get<Admitter>().IsEnabled());
VerifyOrQuit(!admitter.Get<Admitter>().IsPrimeAdmitter());
nexus.AdvanceTime(45 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<Ip6::Filter>().AddUnsecurePort(admitter.Get<Manager>().GetUdpPort()));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Establish a DTLS connection from `enroller` to `admitter`");
sockAddr.SetAddress(admitter.Get<Mle::Mle>().GetLinkLocalAddress());
sockAddr.SetPort(admitter.Get<Manager>().GetUdpPort());
admitter.Get<KeyManager>().GetPskc(pskc);
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SetPsk(pskc.m8, Pskc::kSize));
enroller.Get<Tmf::SecureAgent>().RegisterResourceHandler(HandleResource, &recvContext);
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().Open(0));
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().Connect(sockAddr));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(enroller.Get<Tmf::SecureAgent>().IsConnected());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check that Enroller session list on `admitter` is empty");
iter.Init(admitter.GetInstance());
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerRegister` message from `enroller` to `admitter`");
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
mode = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
steeringData.SetToPermitAllJoiners();
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerId));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, mode));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContext.mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the enroller list on `admitter`");
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
sessionInfo = &enrollerInfo.mSessionInfo;
VerifyOrQuit(sessionInfo->mIsConnected);
VerifyOrQuit(!sessionInfo->mIsCommissioner);
VerifyOrQuit(enroller.Get<ThreadNetif>().HasUnicastAddress(AsCoreType(&sessionInfo->mPeerSockAddr.mAddress)));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `admitter` becomes active commissioner");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `EnrollerReportState` is received with the updated Admitter state");
VerifyOrQuit(recvContext.HasReceivedReportState());
VerifyOrQuit(recvContext.GetLastReportedAdmitterState() == kAdmitterActive);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that commissioner steering data and session ID are properly set");
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16));
VerifyOrQuit(rloc16 == admitter.Get<Mle::Mle>().GetRloc16());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindCommissioningSessionId(sessionId));
VerifyOrQuit(sessionId == admitter.Get<Admitter>().GetCommissionerSessionId());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindSteeringData(leaderSteeringData));
VerifyOrQuit(leaderSteeringData == steeringData);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Ensure no changes before enroller timeout");
recvContext.Clear();
nexus.AdvanceTime((kEnrollerTimeoutInSec - 3) * Time::kOneSecondInMsec);
VerifyOrQuit(!recvContext.HasReceivedReportState());
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(enrollerInfo.mRegisterDuration >= 48 * Time::kOneSecondInMsec);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerKeepAlive` message");
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContext.mHasAdmitterState);
VerifyOrQuit(responseContext.mAdmitterState == kAdmitterActive);
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
VerifyOrQuit(!recvContext.HasReceivedReportState());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Wait until just before the timeout and validate that `admitter` remains active commissioner");
nexus.AdvanceTime((kEnrollerTimeoutInSec - 2) * Time::kOneSecondInMsec);
VerifyOrQuit(!recvContext.HasReceivedReportState());
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerKeepAlive` message with an Enroller Mode TLV changing the mode");
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
mode = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx;
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, mode));
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContext.mHasAdmitterState);
VerifyOrQuit(responseContext.mAdmitterState == kAdmitterActive);
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
VerifyOrQuit(!recvContext.HasReceivedReportState());
nexus.AdvanceTime((kEnrollerTimeoutInSec - 2) * Time::kOneSecondInMsec);
VerifyOrQuit(!recvContext.HasReceivedReportState());
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerKeepAlive` message with Steering Data TLV");
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(steeringData.Init(MeshCoP::SteeringData::kMaxLength));
SuccessOrQuit(steeringData.UpdateBloomFilter(admitter.Get<Mac::Mac>().GetExtAddress()));
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContext.mHasAdmitterState);
VerifyOrQuit(responseContext.mAdmitterState == kAdmitterActive);
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate Network Data is updated with the new Steering Data");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(!recvContext.HasReceivedReportState());
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16));
VerifyOrQuit(rloc16 == admitter.Get<Mle::Mle>().GetRloc16());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindCommissioningSessionId(sessionId));
VerifyOrQuit(sessionId == admitter.Get<Admitter>().GetCommissionerSessionId());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindSteeringData(leaderSteeringData));
VerifyOrQuit(leaderSteeringData == steeringData);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the enroller timeout");
// Already 2 seconds has passed since sending last `EnrollerKeepAlive`
nexus.AdvanceTime((kEnrollerTimeoutInSec - 2) * Time::kOneSecondInMsec - 50);
VerifyOrQuit(!recvContext.HasReceivedReportState());
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
nexus.AdvanceTime(75);
iter.Init(admitter.GetInstance());
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the `admitter` resigns from being active commissioner");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!admitter.Get<Admitter>().IsActiveCommissioner());
VerifyOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16) == kErrorNotFound);
nexus.AdvanceTime(10 * Time::kOneSecondInMsec);
VerifyOrQuit(!enroller.Get<Tmf::SecureAgent>().IsConnected());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Re-establish DTLS session");
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().Connect(sockAddr));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(enroller.Get<Tmf::SecureAgent>().IsConnected());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Register as enroller again");
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerId));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, mode));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContext.mHasAdmitterState);
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `admitter` becomes active commissioner again");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16));
VerifyOrQuit(rloc16 == admitter.Get<Mle::Mle>().GetRloc16());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindCommissioningSessionId(sessionId));
VerifyOrQuit(sessionId == admitter.Get<Admitter>().GetCommissionerSessionId());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindSteeringData(leaderSteeringData));
VerifyOrQuit(leaderSteeringData == steeringData);
nexus.AdvanceTime((kEnrollerTimeoutInSec / 2) * Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerKeepAlive` message from `enroller` with `kReject` status, resigning enroller role");
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kReject));
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kReject);
iter.Init(admitter.GetInstance());
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!admitter.Get<Admitter>().IsActiveCommissioner());
VerifyOrQuit(enroller.Get<Tmf::SecureAgent>().IsConnected());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Register as enroller again");
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerId));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, mode));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContext.mHasAdmitterState);
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
nexus.AdvanceTime((kEnrollerTimeoutInSec / 2) * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerRegister` message while already registered, with different parameters");
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
mode = 0;
SuccessOrQuit(steeringData.Init(8));
SuccessOrQuit(steeringData.UpdateBloomFilter(admitter.Get<Mac::Mac>().GetExtAddress()));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerIdAlt));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, mode));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
Log("Validate that enroller info is updated accordingly");
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerIdAlt));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the Network Data (Commissioner Data) is also updated");
nexus.AdvanceTime(Time::kOneSecondInMsec);
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16));
VerifyOrQuit(rloc16 == admitter.Get<Mle::Mle>().GetRloc16());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindCommissioningSessionId(sessionId));
VerifyOrQuit(sessionId == admitter.Get<Admitter>().GetCommissionerSessionId());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindSteeringData(leaderSteeringData));
VerifyOrQuit(leaderSteeringData == steeringData);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the `EnrollerRegister` extended the keep-alive timeout");
recvContext.Clear();
nexus.AdvanceTime((kEnrollerTimeoutInSec - 3) * Time::kOneSecondInMsec);
VerifyOrQuit(!recvContext.HasReceivedReportState());
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerIdAlt));
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an invalid `EnrollerKeepAlive` message without State TLV and validate that it is rejected");
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kReject);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check that enroller is removed on `admitter`, and it stops being active commissioner");
iter.Init(admitter.GetInstance());
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
nexus.AdvanceTime(10 * Time::kOneSecondInMsec);
VerifyOrQuit(!enroller.Get<Tmf::SecureAgent>().IsConnected());
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!admitter.Get<Admitter>().IsActiveCommissioner());
VerifyOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Re-establish DTLS session");
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().Connect(sockAddr));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(enroller.Get<Tmf::SecureAgent>().IsConnected());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send `EnrollerRegister` with missing TLVs, validate that it is rejected");
for (uint16_t testIter = 0; testIter < 3; testIter++)
{
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
// Skip one of the required TLVs for each `testIter`.
if (testIter != 0)
{
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerId));
}
if (testIter != 1)
{
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, mode));
}
if (testIter != 2)
{
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
}
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(250);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kReject);
iter.Init(admitter.GetInstance());
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Register as enroller with invalid Steering Data, validate that Admitter rejects");
for (uint8_t length = 1; length <= 16; length++)
{
// Steering Data Length 1 can only be used with
// `SetPermitAllJoiners()` or empty. Lengths of 8
// and 16 are valid.
if ((length == 8) || (length == 16))
{
continue;
}
Log("Send `EnrollerRegister` with invalid Steering Data length %u, validate that it is rejected", length);
SuccessOrQuit(steeringData.Init(length));
SuccessOrQuit(steeringData.UpdateBloomFilter(admitter.Get<Mac::Mac>().GetExtAddress()));
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerId));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, mode));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(250);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kReject);
iter.Init(admitter.GetInstance());
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Register as enroller with empty Steering Data, validate that Admitter accepts");
SuccessOrQuit(steeringData.Init(1));
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerId));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, mode));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
responseContext.Clear();
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContext));
nexus.AdvanceTime(250);
VerifyOrQuit(responseContext.mReceived);
VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kAccept);
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
}
//---------------------------------------------------------------------------------------------------------------------
void TestBorderAdmitterCommissionerConflictAndPetitionerRetry(void)
{
static const char kEnrollerId[] = "TestEnroller1234";
Core nexus;
Node &admitter = nexus.CreateNode();
Node &enroller = nexus.CreateNode();
Node &otherCommr = nexus.CreateNode();
Ip6::SockAddr sockAddr;
Pskc pskc;
Admitter::Iterator iter;
Admitter::EnrollerInfo enrollerInfo;
Coap::Message *message;
uint8_t mode;
MeshCoP::SteeringData steeringData;
MeshCoP::SteeringData leaderSteeringData;
ReceiveContext recvContext;
uint16_t rloc16;
uint16_t sessionId;
uint16_t numStateChanges;
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAdmitterCommissionerConflictAndPetitionerRetry");
nexus.AdvanceTime(0);
// Form the topology:
// - `admitter` forms the network (as leader)
// - `otherCommr` joins the same network.
// - `enroller` stays disconnected.
admitter.Form();
nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
otherCommr.Join(admitter);
nexus.AdvanceTime(10 * Time::kOneMinuteInMsec);
VerifyOrQuit(admitter.Get<Mle::Mle>().IsLeader());
VerifyOrQuit(otherCommr.Get<Mle::Mle>().IsRouter());
SuccessOrQuit(enroller.Get<Mac::Mac>().SetPanChannel(admitter.Get<Mac::Mac>().GetPanChannel()));
enroller.Get<Mac::Mac>().SetPanId(admitter.Get<Mac::Mac>().GetPanId());
enroller.Get<ThreadNetif>().Up();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Border Admitter on `admitter`");
admitter.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(admitter.Get<Admitter>().IsEnabled());
VerifyOrQuit(!admitter.Get<Admitter>().IsPrimeAdmitter());
nexus.AdvanceTime(45 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<Ip6::Filter>().AddUnsecurePort(admitter.Get<Manager>().GetUdpPort()));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Make `otherCommr` the active commissioner");
SuccessOrQuit(otherCommr.Get<Commissioner>().Start(nullptr, nullptr, nullptr));
nexus.AdvanceTime(2 * Time::kOneSecondInMsec);
VerifyOrQuit(otherCommr.Get<Commissioner>().GetState() == Commissioner::kStateActive);
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16));
VerifyOrQuit(rloc16 == otherCommr.Get<Mle::Mle>().GetRloc16());
nexus.AdvanceTime(5 * Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Establish a DTLS connection from `enroller` to `admitter`");
sockAddr.SetAddress(admitter.Get<Mle::Mle>().GetLinkLocalAddress());
sockAddr.SetPort(admitter.Get<Manager>().GetUdpPort());
admitter.Get<KeyManager>().GetPskc(pskc);
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SetPsk(pskc.m8, Pskc::kSize));
enroller.Get<Tmf::SecureAgent>().RegisterResourceHandler(HandleResource, &recvContext);
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().Open(0));
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().Connect(sockAddr));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(enroller.Get<Tmf::SecureAgent>().IsConnected());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerRegister` message from `enroller` to `admitter`");
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
mode = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
steeringData.SetToPermitAllJoiners();
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerId));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, mode));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message));
nexus.AdvanceTime(Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the enroller list on `admitter`");
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Since there is another commissioner active, validate that `admitter` fails to become active");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!admitter.Get<Admitter>().IsActiveCommissioner());
VerifyOrQuit(admitter.Get<Admitter>().IsPetitionRejected());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `EnrollerReportState` is received with `ConflictError` state");
VerifyOrQuit(recvContext.HasReceivedReportState());
VerifyOrQuit(recvContext.GetLastReportedAdmitterState() == kAdmitterConflictError);
nexus.AdvanceTime(10 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!admitter.Get<Admitter>().IsActiveCommissioner());
VerifyOrQuit(admitter.Get<Admitter>().IsPetitionRejected());
VerifyOrQuit(otherCommr.Get<Commissioner>().GetState() == Commissioner::kStateActive);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Stop `otherCommr` from acting as active commissioner");
recvContext.Clear();
SuccessOrQuit(otherCommr.Get<Commissioner>().Stop());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the `admitter` will detect this, petitions, and becomes active commissioner");
nexus.AdvanceTime(2 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
VerifyOrQuit(!admitter.Get<Admitter>().IsPetitionRejected());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `EnrollerReportState` is received now with `Active` state");
VerifyOrQuit(recvContext.HasReceivedReportState());
VerifyOrQuit(recvContext.GetLastReportedAdmitterState() == kAdmitterActive);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check the Network Data (Commissioner Data) to be properly set");
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16));
VerifyOrQuit(rloc16 == admitter.Get<Mle::Mle>().GetRloc16());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindCommissioningSessionId(sessionId));
VerifyOrQuit(sessionId == admitter.Get<Admitter>().GetCommissionerSessionId());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindSteeringData(leaderSteeringData));
VerifyOrQuit(leaderSteeringData == steeringData);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("From `otherCommr` forcefully evict the current active commissioner (`admitter`)");
recvContext.Clear();
SuccessOrQuit(otherCommr.Get<Manager>().EvictActiveCommissioner());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send `EnrollerKeepAlive` three times, 20 seconds apart to maintain the enroller connection");
for (uint8_t i = 0; i < 3; i++)
{
nexus.AdvanceTime(20 * Time::kOneSecondInMsec);
message = enroller.Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
SuccessOrQuit(enroller.Get<Tmf::SecureAgent>().SendMessage(*message));
nexus.AdvanceTime(Time::kOneSecondInMsec);
iter.Init(admitter.GetInstance());
SuccessOrQuit(iter.GetNextEnrollerInfo(enrollerInfo));
VerifyOrQuit(StringMatch(enrollerInfo.mId, kEnrollerId));
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNotFound);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the eviction was properly detected, and petitioner retry mechanism did restore it");
VerifyOrQuit(recvContext.HasReceivedReportState());
VerifyOrQuit(recvContext.GetLastReportedAdmitterState() == kAdmitterActive);
numStateChanges = recvContext.mStateReports.GetLength();
VerifyOrQuit(numStateChanges >= 2);
VerifyOrQuit(recvContext.mStateReports[numStateChanges - 2].mAdmitterState == kAdmitterReady);
VerifyOrQuit(recvContext.mStateReports[numStateChanges - 1].mAdmitterState == kAdmitterActive);
}
//---------------------------------------------------------------------------------------------------------------------
template <uint8_t kNumEnrollers>
uint8_t FindMatchingEnroller(const Admitter::EnrollerInfo &aInfo,
const char *(&aEnrollerIds)[kNumEnrollers],
BitSet<kNumEnrollers> &aFoundIndexes)
{
// Finds a matching enroller by comparing `aInfo.mId` against
// `aEnrollerIds[]` and returns the matched index. This function
// ensures each enroller is found only once by checking that the
// index is not already in `aFoundIndexes`, then updates
// `aFoundIndexes`.
uint8_t matchedIndex = kNumEnrollers;
for (uint8_t index = 0; index < kNumEnrollers; index++)
{
if (StringMatch(aInfo.mId, aEnrollerIds[index]))
{
matchedIndex = index;
break;
}
}
VerifyOrQuit(matchedIndex < kNumEnrollers);
VerifyOrQuit(!aFoundIndexes.Has(matchedIndex));
aFoundIndexes.Add(matchedIndex);
return matchedIndex;
}
template <uint8_t kNumEnrollers> bool DidFindAllEnrollers(const BitSet<kNumEnrollers> &aFoundIndexes)
{
bool didFindAll = true;
for (uint8_t index = 0; index < kNumEnrollers; index++)
{
if (!aFoundIndexes.Has(index))
{
didFindAll = false;
break;
}
}
return didFindAll;
}
//---------------------------------------------------------------------------------------------------------------------
void LogEnroller(const Admitter::EnrollerInfo &aInfo)
{
Log(" Enroller - id:%s steeringData:%s mode:0x%02x[%c%c]", aInfo.mId,
AsCoreType(&aInfo.mSteeringData).ToString().AsCString(), aInfo.mMode,
aInfo.mMode & MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx ? 'J' : '-',
aInfo.mMode & MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx ? 'U' : '-');
}
void LogJoiner(const Admitter::JoinerInfo &aInfo)
{
Log(" Joiner - iid:%s, msec-till-expire:%lu", AsCoreType(&aInfo.mIid).ToString().AsCString(),
ToUlong(aInfo.mMsecTillExpiration));
}
//---------------------------------------------------------------------------------------------------------------------
void TestBorderAdmitterMultipleEnrollers(void)
{
static constexpr uint8_t kNumEnrollers = 4;
static const char *kEnrollerIds[kNumEnrollers] = {"earth", "water", "wind", "fire"};
Core nexus;
Node &admitter = nexus.CreateNode();
Node *enrollers[kNumEnrollers];
Ip6::SockAddr sockAddr;
Pskc pskc;
Admitter::Iterator iter;
Admitter::EnrollerInfo enrollerInfo;
Admitter::JoinerInfo joinerInfo;
Coap::Message *message;
uint8_t mode;
MeshCoP::SteeringData steeringData[kNumEnrollers];
ReceiveContext recvContext[kNumEnrollers];
ResponseContext responseContexts[kNumEnrollers];
BitSet<kNumEnrollers> foundEnrollers;
MeshCoP::SteeringData leaderSteeringData;
MeshCoP::SteeringData combinedSteeringData;
uint16_t rloc16;
uint16_t sessionId;
Mac::ExtAddress joinerIid;
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAdmitterMultipleEnrollers");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
enrollers[i] = &nexus.CreateNode();
}
nexus.AdvanceTime(0);
// Form the topology:
// - `admitter` forms the network (as leader)
// - All enrollers stay disconnected.
admitter.Form();
nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Mle::Mle>().IsLeader());
for (Node *enroller : enrollers)
{
SuccessOrQuit(enroller->Get<Mac::Mac>().SetPanChannel(admitter.Get<Mac::Mac>().GetPanChannel()));
enroller->Get<Mac::Mac>().SetPanId(admitter.Get<Mac::Mac>().GetPanId());
enroller->Get<ThreadNetif>().Up();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Border Admitter on `admitter`");
admitter.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(admitter.Get<Admitter>().IsEnabled());
VerifyOrQuit(!admitter.Get<Admitter>().IsPrimeAdmitter());
nexus.AdvanceTime(45 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<Ip6::Filter>().AddUnsecurePort(admitter.Get<Manager>().GetUdpPort()));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Establish a DTLS connection from all `enrollers` to `admitter`");
sockAddr.SetAddress(admitter.Get<Mle::Mle>().GetLinkLocalAddress());
sockAddr.SetPort(admitter.Get<Manager>().GetUdpPort());
admitter.Get<KeyManager>().GetPskc(pskc);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Node *enroller = enrollers[i];
SuccessOrQuit(enroller->Get<Tmf::SecureAgent>().SetPsk(pskc.m8, Pskc::kSize));
recvContext[i].Clear();
enroller->Get<Tmf::SecureAgent>().RegisterResourceHandler(HandleResource, &recvContext[i]);
SuccessOrQuit(enroller->Get<Tmf::SecureAgent>().Open(0));
SuccessOrQuit(enroller->Get<Tmf::SecureAgent>().Connect(sockAddr));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(enroller->Get<Tmf::SecureAgent>().IsConnected());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Prepare Steering Data for each enroller");
mode = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
steeringData[0].SetToPermitAllJoiners();
SuccessOrQuit(steeringData[1].Init(16));
for (uint8_t numJoiners = 0; numJoiners < 3; numJoiners++)
{
joinerIid.GenerateRandom();
SuccessOrQuit(steeringData[1].UpdateBloomFilter(joinerIid));
}
SuccessOrQuit(steeringData[2].Init(8));
joinerIid.GenerateRandom();
SuccessOrQuit(steeringData[2].UpdateBloomFilter(joinerIid));
SuccessOrQuit(steeringData[3].Init(8));
joinerIid.GenerateRandom();
SuccessOrQuit(steeringData[3].UpdateBloomFilter(joinerIid));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerRegister` message from all `enrollers`");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
message =
enrollers[i]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerIds[i]));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, mode));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData[i]));
responseContexts[i].Clear();
SuccessOrQuit(
enrollers[i]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[i]));
}
nexus.AdvanceTime(Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check that all registrations were accepted");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
VerifyOrQuit(responseContexts[i].mReceived);
VerifyOrQuit(responseContexts[i].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[i].mHasAdmitterState);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `admitter` becomes active commissioner");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16));
VerifyOrQuit(rloc16 == admitter.Get<Mle::Mle>().GetRloc16());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindCommissioningSessionId(sessionId));
VerifyOrQuit(sessionId == admitter.Get<Admitter>().GetCommissionerSessionId());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindSteeringData(leaderSteeringData));
VerifyOrQuit(leaderSteeringData.GetLength() == 1);
VerifyOrQuit(leaderSteeringData.PermitsAllJoiners());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `EnrollerReportState` is received with the updated Admitter state");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
// Some enrollers may already get the updated state in the Register response
if (recvContext[i].HasReceivedReportState())
{
VerifyOrQuit(recvContext[i].GetLastReportedAdmitterState() == kAdmitterActive);
}
}
// - - - - - - - - - - - - - - - - - - - - - - -. - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the enroller list on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData[matchedIndex]);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
nexus.AdvanceTime(10 * Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send a keep alive from first enroller with reject status (to unregister the enroller)");
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kReject));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kReject);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check enroller info and that enroller 0 is no longer present");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData[matchedIndex]);
VerifyOrQuit(enrollerInfo.mMode == mode);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(!foundEnrollers.Has(0));
VerifyOrQuit(foundEnrollers.Has(1));
VerifyOrQuit(foundEnrollers.Has(2));
VerifyOrQuit(foundEnrollers.Has(3));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `admitter` is still active commissioner and the steering data is updated");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16));
VerifyOrQuit(rloc16 == admitter.Get<Mle::Mle>().GetRloc16());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindCommissioningSessionId(sessionId));
VerifyOrQuit(sessionId == admitter.Get<Admitter>().GetCommissionerSessionId());
combinedSteeringData = steeringData[1];
SuccessOrQuit(combinedSteeringData.MergeBloomFilterWith(steeringData[2]));
SuccessOrQuit(combinedSteeringData.MergeBloomFilterWith(steeringData[3]));
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindSteeringData(leaderSteeringData));
VerifyOrQuit(leaderSteeringData == combinedSteeringData);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Wait 20 seconds, send keep-alive from enrollers[2,3] but not from [1]");
nexus.AdvanceTime(20 * Time::kOneSecondInMsec);
for (uint8_t i = 2; i < kNumEnrollers; i++)
{
message =
enrollers[i]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
responseContexts[i].Clear();
SuccessOrQuit(
enrollers[i]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[i]));
}
nexus.AdvanceTime(Time::kOneSecondInMsec);
for (uint8_t i = 2; i < kNumEnrollers; i++)
{
VerifyOrQuit(responseContexts[i].mReceived);
VerifyOrQuit(responseContexts[i].mResponseState == MeshCoP::StateTlv::kAccept);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Wait 35 more seconds and check that enroller 1 is now removed");
nexus.AdvanceTime(35 * Time::kOneSecondInMsec);
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
LogEnroller(enrollerInfo);
FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(!foundEnrollers.Has(0));
VerifyOrQuit(!foundEnrollers.Has(1));
VerifyOrQuit(foundEnrollers.Has(2));
VerifyOrQuit(foundEnrollers.Has(3));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `admitter` updates the steering data");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
combinedSteeringData = steeringData[2];
SuccessOrQuit(combinedSteeringData.MergeBloomFilterWith(steeringData[3]));
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindSteeringData(leaderSteeringData));
VerifyOrQuit(leaderSteeringData == combinedSteeringData);
}
//---------------------------------------------------------------------------------------------------------------------
void TestBorderAdmitterJoinerEnrollerInteraction(void)
{
static constexpr uint8_t kNumEnrollers = 4;
static constexpr uint8_t kNumJoiners = 2;
static const char *kEnrollerIds[kNumEnrollers] = {"diamond", "ruby", "sapphire", "emerald"};
static const char kPskd[] = "J01NME1234";
Core nexus;
Node &admitter = nexus.CreateNode();
Node *enrollers[kNumEnrollers];
Node *joiners[kNumJoiners];
Ip6::SockAddr sockAddr;
Pskc pskc;
Coap::Message *message;
uint8_t modes[kNumEnrollers];
ResponseContext responseContexts[kNumEnrollers];
ReceiveContext recvContext[kNumEnrollers];
MeshCoP::SteeringData steeringData;
MeshCoP::SteeringData leaderSteeringData;
Ip6::InterfaceIdentifier joinerIids[kNumJoiners];
Ip6::InterfaceIdentifier wildcardJoinerIid;
Admitter::Iterator iter;
Admitter::EnrollerInfo enrollerInfo;
Admitter::JoinerInfo joinerInfo;
BitSet<kNumEnrollers> foundEnrollers;
BitSet<kNumJoiners> foundJoiners;
uint16_t sessionId;
uint16_t rloc16;
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAdmitterJoinerEnrollerInteraction");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
enrollers[i] = &nexus.CreateNode();
}
for (uint8_t j = 0; j < kNumJoiners; j++)
{
joiners[j] = &nexus.CreateNode();
}
nexus.AdvanceTime(0);
// Form the topology:
// - `admitter` forms the network (as leader)
// - All enrollers stay disconnected.
admitter.Form();
nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Mle::Mle>().IsLeader());
for (Node *enroller : enrollers)
{
SuccessOrQuit(enroller->Get<Mac::Mac>().SetPanChannel(admitter.Get<Mac::Mac>().GetPanChannel()));
enroller->Get<Mac::Mac>().SetPanId(admitter.Get<Mac::Mac>().GetPanId());
enroller->Get<ThreadNetif>().Up();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Border Admitter on `admitter`");
admitter.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(admitter.Get<Admitter>().IsEnabled());
VerifyOrQuit(!admitter.Get<Admitter>().IsPrimeAdmitter());
nexus.AdvanceTime(45 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<Ip6::Filter>().AddUnsecurePort(admitter.Get<Manager>().GetUdpPort()));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Establish a DTLS connection from all `enrollers` to `admitter`");
sockAddr.SetAddress(admitter.Get<Mle::Mle>().GetLinkLocalAddress());
sockAddr.SetPort(admitter.Get<Manager>().GetUdpPort());
admitter.Get<KeyManager>().GetPskc(pskc);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Node *enroller = enrollers[i];
SuccessOrQuit(enroller->Get<Tmf::SecureAgent>().SetPsk(pskc.m8, Pskc::kSize));
recvContext[i].Clear();
enroller->Get<Tmf::SecureAgent>().RegisterResourceHandler(HandleResource, &recvContext[i]);
SuccessOrQuit(enroller->Get<Tmf::SecureAgent>().Open(0));
SuccessOrQuit(enroller->Get<Tmf::SecureAgent>().Connect(sockAddr));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(enroller->Get<Tmf::SecureAgent>().IsConnected());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Prepare mode for each enroller");
modes[0] = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
modes[1] = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
modes[2] = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx;
modes[3] = MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
steeringData.SetToPermitAllJoiners();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerRegister` message from all `enrollers`");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
message =
enrollers[i]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerIds[i]));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, modes[i]));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
responseContexts[i].Clear();
SuccessOrQuit(
enrollers[i]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[i]));
}
nexus.AdvanceTime(Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check that all registrations were accepted");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
VerifyOrQuit(responseContexts[i].mReceived);
VerifyOrQuit(responseContexts[i].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[i].mHasAdmitterState);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `admitter` becomes active commissioner");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16));
VerifyOrQuit(rloc16 == admitter.Get<Mle::Mle>().GetRloc16());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindCommissioningSessionId(sessionId));
VerifyOrQuit(sessionId == admitter.Get<Admitter>().GetCommissionerSessionId());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindSteeringData(leaderSteeringData));
VerifyOrQuit(leaderSteeringData == steeringData);
VerifyOrQuit(leaderSteeringData.PermitsAllJoiners());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the Enroller info on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start `joiners[0]`");
joiners[0]->Get<ThreadNetif>().Up();
SuccessOrQuit(joiners[0]->Get<Joiner>().Start(kPskd,
/* aProvisioningUrl */ nullptr,
/* aVendorName */ nullptr,
/* aVendorModel */ nullptr,
/* aVendorSwVersion */ nullptr,
/* aVendorData */ nullptr,
/* aCallback */ nullptr,
/* aContext */ nullptr));
joinerIids[0].SetFromExtAddress(joiners[0]->Get<Joiner>().GetId());
nexus.AdvanceTime(8 * Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `joiner` `RelayRx` are forwarded to all `enrollers` with `kForwardJoinerRelayRx` mode flag");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::InterfaceIdentifier readIid;
uint16_t joinerRouterRloc;
message = AsCoapMessagePtr(recvContext[i].mRelayRxMsgs.GetHead());
if ((modes[i] & MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx) == 0)
{
VerifyOrQuit(message == nullptr);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(*message, readIid));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerRouterLocatorTlv>(*message, joinerRouterRloc));
VerifyOrQuit(readIid == joinerIids[0]);
VerifyOrQuit(joinerRouterRloc == admitter.Get<Mle::Mle>().GetRloc16());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Stop `joiners[0]`");
joiners[0]->Get<Joiner>().Stop();
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerJoinerAccept` message from `enrollers[0]` to `admitter` accepting `joiners[0]`");
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerAccept);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, joinerIids[0]));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[0].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the accepted `joiners[0]` is tracked by `enrollers[0]` entry on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 0)
{
SuccessOrQuit(iter.GetNextJoinerInfo(joinerInfo));
VerifyOrQuit(AsCoreType(&joinerInfo.mIid) == joinerIids[0]);
LogJoiner(joinerInfo);
}
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
nexus.AdvanceTime(Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start `joiners[0]` again and validate that its `RelayRx` are only forwarded to `enrollers[0]`");
joiners[0]->Get<ThreadNetif>().Up();
SuccessOrQuit(joiners[0]->Get<Joiner>().Start(kPskd,
/* aProvisioningUrl */ nullptr,
/* aVendorName */ nullptr,
/* aVendorModel */ nullptr,
/* aVendorSwVersion */ nullptr,
/* aVendorData */ nullptr,
/* aCallback */ nullptr,
/* aContext */ nullptr));
nexus.AdvanceTime(8 * Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::InterfaceIdentifier readIid;
uint16_t joinerRouterRloc;
message = AsCoapMessagePtr(recvContext[i].mRelayRxMsgs.GetHead());
if (i != 0)
{
VerifyOrQuit(message == nullptr);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(*message, readIid));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerRouterLocatorTlv>(*message, joinerRouterRloc));
VerifyOrQuit(readIid == joinerIids[0]);
VerifyOrQuit(joinerRouterRloc == admitter.Get<Mle::Mle>().GetRloc16());
}
joiners[0]->Get<Joiner>().Stop();
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start `joiners[1]` and validate that its `RelayRx` are forwarded to all `enrollers`");
joiners[1]->Get<ThreadNetif>().Up();
SuccessOrQuit(joiners[1]->Get<Joiner>().Start(kPskd,
/* aProvisioningUrl */ nullptr,
/* aVendorName */ nullptr,
/* aVendorModel */ nullptr,
/* aVendorSwVersion */ nullptr,
/* aVendorData */ nullptr,
/* aCallback */ nullptr,
/* aContext */ nullptr));
joinerIids[1].SetFromExtAddress(joiners[1]->Get<Joiner>().GetId());
nexus.AdvanceTime(8 * Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::InterfaceIdentifier readIid;
uint16_t joinerRouterRloc;
message = AsCoapMessagePtr(recvContext[i].mRelayRxMsgs.GetHead());
if ((modes[i] & MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx) == 0)
{
VerifyOrQuit(message == nullptr);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(*message, readIid));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerRouterLocatorTlv>(*message, joinerRouterRloc));
VerifyOrQuit(readIid == joinerIids[1]);
VerifyOrQuit(joinerRouterRloc == admitter.Get<Mle::Mle>().GetRloc16());
}
joiners[1]->Get<Joiner>().Stop();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send `EnrollerKeepAlive` message from `enrollers[0]` change its mode to disallow `kForwardJoinerRelayRx`");
modes[0] = MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, modes[0]));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[0].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the `enrollers[0]` mode got changed on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 0)
{
SuccessOrQuit(iter.GetNextJoinerInfo(joinerInfo));
VerifyOrQuit(AsCoreType(&joinerInfo.mIid) == joinerIids[0]);
LogJoiner(joinerInfo);
}
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
nexus.AdvanceTime(Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start `joiners[0]` again and validate that its `RelayRx` is still only forwarded to `enrollers[0]`");
joiners[0]->Get<ThreadNetif>().Up();
SuccessOrQuit(joiners[0]->Get<Joiner>().Start(kPskd,
/* aProvisioningUrl */ nullptr,
/* aVendorName */ nullptr,
/* aVendorModel */ nullptr,
/* aVendorSwVersion */ nullptr,
/* aVendorData */ nullptr,
/* aCallback */ nullptr,
/* aContext */ nullptr));
nexus.AdvanceTime(8 * Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::InterfaceIdentifier readIid;
uint16_t joinerRouterRloc;
message = AsCoapMessagePtr(recvContext[i].mRelayRxMsgs.GetHead());
if (i != 0)
{
VerifyOrQuit(message == nullptr);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(*message, readIid));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerRouterLocatorTlv>(*message, joinerRouterRloc));
VerifyOrQuit(readIid == joinerIids[0]);
VerifyOrQuit(joinerRouterRloc == admitter.Get<Mle::Mle>().GetRloc16());
}
joiners[0]->Get<Joiner>().Stop();
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start `joiners[1]` and validate that its `RelayRx` is not longer forwarded to `enrollers[0]`");
joiners[1]->Get<ThreadNetif>().Up();
SuccessOrQuit(joiners[1]->Get<Joiner>().Start(kPskd,
/* aProvisioningUrl */ nullptr,
/* aVendorName */ nullptr,
/* aVendorModel */ nullptr,
/* aVendorSwVersion */ nullptr,
/* aVendorData */ nullptr,
/* aCallback */ nullptr,
/* aContext */ nullptr));
joinerIids[1].SetFromExtAddress(joiners[1]->Get<Joiner>().GetId());
nexus.AdvanceTime(8 * Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::InterfaceIdentifier readIid;
uint16_t joinerRouterRloc;
message = AsCoapMessagePtr(recvContext[i].mRelayRxMsgs.GetHead());
if ((modes[i] & MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx) == 0)
{
VerifyOrQuit(message == nullptr);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(*message, readIid));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerRouterLocatorTlv>(*message, joinerRouterRloc));
VerifyOrQuit(readIid == joinerIids[1]);
VerifyOrQuit(joinerRouterRloc == admitter.Get<Mle::Mle>().GetRloc16());
}
joiners[1]->Get<Joiner>().Stop();
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send `EnrollerKeepAlive` message from `enrollers[0]` to revert its mode back to allowing both Joiner and UDP");
modes[0] = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, modes[0]));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[0].mHasAdmitterState);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the `enrollers[0]` mode got changed on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 0)
{
SuccessOrQuit(iter.GetNextJoinerInfo(joinerInfo));
VerifyOrQuit(AsCoreType(&joinerInfo.mIid) == joinerIids[0]);
LogJoiner(joinerInfo);
}
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
nexus.AdvanceTime(Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send `EnrollerKeepAlive` message from all `enrollers` to maintain the connection");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
message =
enrollers[i]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
responseContexts[i].Clear();
SuccessOrQuit(
enrollers[i]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[i]));
}
nexus.AdvanceTime(Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
VerifyOrQuit(responseContexts[i].mReceived);
VerifyOrQuit(responseContexts[i].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[i].mHasAdmitterState);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerJoinerAccept` message from `enrollers[0]` to `admitter` accepting `joiners[1]`");
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerAccept);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, joinerIids[1]));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[0].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that both accepted `joiners` are tracked by `enrollers[0]` on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 0)
{
uint16_t numJoiners = 0;
foundJoiners.Clear();
while (iter.GetNextJoinerInfo(joinerInfo) == kErrorNone)
{
LogJoiner(joinerInfo);
numJoiners++;
for (uint8_t j = 0; j < 2; j++)
{
if (joinerIids[j] == AsCoreType(&joinerInfo.mIid))
{
VerifyOrQuit(!foundJoiners.Has(j));
foundJoiners.Add(j);
}
}
}
VerifyOrQuit(numJoiners == 2);
}
else
{
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start `joiners[1]` again and validate that its `RelayRx` are only forwarded to `enrollers[0]`");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
joiners[1]->Get<ThreadNetif>().Up();
SuccessOrQuit(joiners[1]->Get<Joiner>().Start(kPskd,
/* aProvisioningUrl */ nullptr,
/* aVendorName */ nullptr,
/* aVendorModel */ nullptr,
/* aVendorSwVersion */ nullptr,
/* aVendorData */ nullptr,
/* aCallback */ nullptr,
/* aContext */ nullptr));
nexus.AdvanceTime(8 * Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::InterfaceIdentifier readIid;
uint16_t joinerRouterRloc;
message = AsCoapMessagePtr(recvContext[i].mRelayRxMsgs.GetHead());
if (i != 0)
{
VerifyOrQuit(message == nullptr);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(*message, readIid));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerRouterLocatorTlv>(*message, joinerRouterRloc));
VerifyOrQuit(readIid == joinerIids[1]);
VerifyOrQuit(joinerRouterRloc == admitter.Get<Mle::Mle>().GetRloc16());
}
joiners[1]->Get<Joiner>().Stop();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("From `enrollers[1]` send `EnrollerJoinerAccept` for `joiners[1]`");
message =
enrollers[1]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerAccept);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, joinerIids[1]));
responseContexts[1].Clear();
SuccessOrQuit(enrollers[1]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[1]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
Log("Validate that the request is rejected since `joiners[1]` is already accepted by `enrollers[0]`");
VerifyOrQuit(responseContexts[1].mReceived);
VerifyOrQuit(responseContexts[1].mResponseState == MeshCoP::StateTlv::kReject);
VerifyOrQuit(responseContexts[1].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate `joiners[1]` is still accepted by `enrollers[0]`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 0)
{
uint16_t numJoiners = 0;
foundJoiners.Clear();
while (iter.GetNextJoinerInfo(joinerInfo) == kErrorNone)
{
LogJoiner(joinerInfo);
numJoiners++;
for (uint8_t j = 0; j < 2; j++)
{
if (joinerIids[j] == AsCoreType(&joinerInfo.mIid))
{
VerifyOrQuit(!foundJoiners.Has(j));
foundJoiners.Add(j);
}
}
}
VerifyOrQuit(numJoiners == 2);
}
else
{
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start `joiners[1]` again and validate that its `RelayRx` are only forwarded to `enrollers[0]`");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
joiners[1]->Get<ThreadNetif>().Up();
SuccessOrQuit(joiners[1]->Get<Joiner>().Start(kPskd,
/* aProvisioningUrl */ nullptr,
/* aVendorName */ nullptr,
/* aVendorModel */ nullptr,
/* aVendorSwVersion */ nullptr,
/* aVendorData */ nullptr,
/* aCallback */ nullptr,
/* aContext */ nullptr));
nexus.AdvanceTime(8 * Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::InterfaceIdentifier readIid;
uint16_t joinerRouterRloc;
message = AsCoapMessagePtr(recvContext[i].mRelayRxMsgs.GetHead());
if (i != 0)
{
VerifyOrQuit(message == nullptr);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(*message, readIid));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerRouterLocatorTlv>(*message, joinerRouterRloc));
VerifyOrQuit(readIid == joinerIids[1]);
VerifyOrQuit(joinerRouterRloc == admitter.Get<Mle::Mle>().GetRloc16());
}
joiners[1]->Get<Joiner>().Stop();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerJoinerAccept` message again accepting `joiners[1]` from `enrollers[0]`");
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerAccept);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, joinerIids[1]));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[0].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that there is no change in the `enrollers` list and the tracked `joiners` on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 0)
{
uint16_t numJoiners = 0;
foundJoiners.Clear();
while (iter.GetNextJoinerInfo(joinerInfo) == kErrorNone)
{
LogJoiner(joinerInfo);
numJoiners++;
for (uint8_t j = 0; j < 2; j++)
{
if (joinerIids[j] == AsCoreType(&joinerInfo.mIid))
{
VerifyOrQuit(!foundJoiners.Has(j));
foundJoiners.Add(j);
}
}
}
VerifyOrQuit(numJoiners == 2);
}
else
{
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerJoinerRelease` message from `enrollers[0]` to `admitter` releasing `joiners[0]`");
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerRelease);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, joinerIids[0]));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[0].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that the released `joiners[0]` is removed on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 0)
{
SuccessOrQuit(iter.GetNextJoinerInfo(joinerInfo));
VerifyOrQuit(AsCoreType(&joinerInfo.mIid) == joinerIids[1]);
LogJoiner(joinerInfo);
}
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
nexus.AdvanceTime(Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerJoinerRelease` message again releasing `joiners[0]` from `enrollers[0]`");
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerRelease);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, joinerIids[0]));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
Log("Validate that `EnrollerJoinerRelease` is accepted, even though the given IID is already removed");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[0].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerJoinerRelease` message releasing `joiners[1]` from `enrollers[0]`");
message =
enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerRelease);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, joinerIids[1]));
responseContexts[0].Clear();
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[0]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[0].mReceived);
VerifyOrQuit(responseContexts[0].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[0].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send two `EnrollerJoinerAccept` messages from `enrollers[2]` accepting both `joiners`");
for (uint8_t j = 0; j < 2; j++)
{
message = enrollers[2]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(
kUriEnrollerJoinerAccept);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, joinerIids[j]));
responseContexts[2].Clear();
SuccessOrQuit(
enrollers[2]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[2]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[2].mReceived);
VerifyOrQuit(responseContexts[2].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[2].mHasAdmitterState);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that both accepted joiners are tracked by `enrollers[2]` on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 2)
{
uint16_t numJoiners = 0;
foundJoiners.Clear();
while (iter.GetNextJoinerInfo(joinerInfo) == kErrorNone)
{
LogJoiner(joinerInfo);
numJoiners++;
for (uint8_t j = 0; j < 2; j++)
{
if (joinerIids[j] == AsCoreType(&joinerInfo.mIid))
{
VerifyOrQuit(!foundJoiners.Has(j));
foundJoiners.Add(j);
}
}
}
VerifyOrQuit(numJoiners == 2);
}
else
{
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerJoinerRelease` message from `enrollers[2]` with wildcard IID releasing all joiners");
message =
enrollers[2]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerRelease);
VerifyOrQuit(message != nullptr);
wildcardJoinerIid.Clear();
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, wildcardJoinerIid));
responseContexts[2].Clear();
SuccessOrQuit(enrollers[2]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[2]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[2].mReceived);
VerifyOrQuit(responseContexts[2].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[2].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that all previously accepted joiners by `enrollers[2]` on `admitter` are now removed");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an invalid `EnrollerJoinerAccept` message from enrollers[2] with wildcard IID");
message =
enrollers[2]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerAccept);
VerifyOrQuit(message != nullptr);
wildcardJoinerIid.Clear();
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, wildcardJoinerIid));
responseContexts[2].Clear();
SuccessOrQuit(enrollers[2]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[2]));
Log("Validate that the invalid `EnrollerJoinerAccept` is correctly rejected");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[2].mReceived);
VerifyOrQuit(responseContexts[2].mResponseState == MeshCoP::StateTlv::kReject);
VerifyOrQuit(responseContexts[2].mHasAdmitterState);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send two `EnrollerJoinerAccept` messages from `enrollers[2]` accepting both `joiners`");
for (uint8_t j = 0; j < 2; j++)
{
message = enrollers[2]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(
kUriEnrollerJoinerAccept);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::JoinerIidTlv>(*message, joinerIids[j]));
responseContexts[2].Clear();
SuccessOrQuit(
enrollers[2]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[2]));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(responseContexts[2].mReceived);
VerifyOrQuit(responseContexts[2].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[2].mHasAdmitterState);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that both accepted Joiners are tracked by `enrollers[2]` on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 2)
{
uint16_t numJoiners = 0;
foundJoiners.Clear();
while (iter.GetNextJoinerInfo(joinerInfo) == kErrorNone)
{
LogJoiner(joinerInfo);
numJoiners++;
for (uint8_t j = 0; j < 2; j++)
{
if (joinerIids[j] == AsCoreType(&joinerInfo.mIid))
{
VerifyOrQuit(!foundJoiners.Has(j));
foundJoiners.Add(j);
}
}
VerifyOrQuit(joinerInfo.mMsecTillExpiration >= 6 * Time::kOneMinuteInMsec);
}
VerifyOrQuit(numJoiners == 2);
}
else
{
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Wait for 4 minutes, sending `EnrollerKeepAlive` every 30 seconds to maintain enroller connections");
for (uint8_t interval = 0; interval < 4 * 2; interval++)
{
Log("Send `EnrollerKeepAlive` message from all `enrollers` to maintain the connection");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
message = enrollers[i]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(
kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
responseContexts[i].Clear();
SuccessOrQuit(
enrollers[i]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[i]));
}
nexus.AdvanceTime(Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
VerifyOrQuit(responseContexts[i].mReceived);
VerifyOrQuit(responseContexts[i].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[i].mHasAdmitterState);
}
nexus.AdvanceTime(29 * Time::kOneSecondInMsec);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the enroller list on `admitter` and that both joiners are still accepted by `enrollers[2]`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 2)
{
uint16_t numJoiners = 0;
foundJoiners.Clear();
while (iter.GetNextJoinerInfo(joinerInfo) == kErrorNone)
{
LogJoiner(joinerInfo);
numJoiners++;
for (uint8_t j = 0; j < 2; j++)
{
if (joinerIids[j] == AsCoreType(&joinerInfo.mIid))
{
VerifyOrQuit(!foundJoiners.Has(j));
foundJoiners.Add(j);
}
}
// Since we waited for 4 minutes, the joiner expiration time should be closer
VerifyOrQuit(joinerInfo.mMsecTillExpiration < 6 * Time::kOneMinuteInMsec);
}
VerifyOrQuit(numJoiners == 2);
}
else
{
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Start `joiners[1]` and validate that its RelayRx are only forwarded to `enrollers[2]`");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
recvContext[i].Clear();
}
joiners[1]->Get<ThreadNetif>().Up();
SuccessOrQuit(joiners[1]->Get<Joiner>().Start(kPskd,
/* aProvisioningUrl */ nullptr,
/* aVendorName */ nullptr,
/* aVendorModel */ nullptr,
/* aVendorSwVersion */ nullptr,
/* aVendorData */ nullptr,
/* aCallback */ nullptr,
/* aContext */ nullptr));
nexus.AdvanceTime(8 * Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::InterfaceIdentifier readIid;
uint16_t joinerRouterRloc;
message = AsCoapMessagePtr(recvContext[i].mRelayRxMsgs.GetHead());
if (i != 2)
{
VerifyOrQuit(message == nullptr);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerIidTlv>(*message, readIid));
SuccessOrQuit(Tlv::Find<MeshCoP::JoinerRouterLocatorTlv>(*message, joinerRouterRloc));
VerifyOrQuit(readIid == joinerIids[1]);
VerifyOrQuit(joinerRouterRloc == admitter.Get<Mle::Mle>().GetRloc16());
}
joiners[1]->Get<Joiner>().Stop();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate expiration is extended for joiners[1] after it transmitted");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 2)
{
uint16_t numJoiners = 0;
foundJoiners.Clear();
while (iter.GetNextJoinerInfo(joinerInfo) == kErrorNone)
{
LogJoiner(joinerInfo);
numJoiners++;
for (uint8_t j = 0; j < 2; j++)
{
if (joinerIids[j] == AsCoreType(&joinerInfo.mIid))
{
VerifyOrQuit(!foundJoiners.Has(j));
foundJoiners.Add(j);
// `joiners[0]` expiration should still tick down, while `joiners[1]`'s should be extended
if (j == 0)
{
VerifyOrQuit(joinerInfo.mMsecTillExpiration < 6 * Time::kOneMinuteInMsec);
}
else
{
VerifyOrQuit(joinerInfo.mMsecTillExpiration >= 6 * Time::kOneMinuteInMsec);
}
}
}
}
VerifyOrQuit(numJoiners == 2);
}
else
{
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Wait for another 4 minutes, sending `EnrollerKeepAlive` every 30 seconds");
for (uint8_t interval = 0; interval < 4 * 2; interval++)
{
Log("Send `EnrollerKeepAlive` message from all `enrollers` to maintain the connection");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
message = enrollers[i]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(
kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
responseContexts[i].Clear();
SuccessOrQuit(
enrollers[i]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[i]));
}
nexus.AdvanceTime(Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
VerifyOrQuit(responseContexts[i].mReceived);
VerifyOrQuit(responseContexts[i].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[i].mHasAdmitterState);
}
nexus.AdvanceTime(29 * Time::kOneSecondInMsec);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `joiners[0]` is timed out and removed on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
if (matchedIndex == 2)
{
SuccessOrQuit(iter.GetNextJoinerInfo(joinerInfo));
LogJoiner(joinerInfo);
VerifyOrQuit(joinerIids[1] == AsCoreType(&joinerInfo.mIid));
VerifyOrQuit(joinerInfo.mMsecTillExpiration > 0);
}
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Wait for another 4 minutes, sending `EnrollerKeepAlive` every 30 seconds");
for (uint8_t interval = 0; interval < 4 * 2; interval++)
{
Log("Send `EnrollerKeepAlive` message from all `enrollers` to maintain the connection");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
message = enrollers[i]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(
kUriEnrollerKeepAlive);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::StateTlv>(*message, MeshCoP::StateTlv::kAccept));
responseContexts[i].Clear();
SuccessOrQuit(
enrollers[i]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[i]));
}
nexus.AdvanceTime(Time::kOneSecondInMsec);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
VerifyOrQuit(responseContexts[i].mReceived);
VerifyOrQuit(responseContexts[i].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[i].mHasAdmitterState);
}
nexus.AdvanceTime(29 * Time::kOneSecondInMsec);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `joiners[1]` is also timed out and removed on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
}
void TestBorderAdmitterForwardingUdpProxy(void)
{
static constexpr uint8_t kNumEnrollers = 4;
static const char *kEnrollerIds[kNumEnrollers] = {"1", "2", "3", "4"};
static const uint8_t kDiagTlvs[] = {NetDiag::Tlv::kExtMacAddress, NetDiag::Tlv::kVersion};
Core nexus;
Node &admitter = nexus.CreateNode();
Node *enrollers[kNumEnrollers];
Ip6::SockAddr sockAddr;
Pskc pskc;
Coap::Message *message;
Coap::Message *diagMessage;
uint8_t modes[kNumEnrollers];
ResponseContext responseContexts[kNumEnrollers];
ReceiveContext recvContext[kNumEnrollers];
MeshCoP::SteeringData steeringData;
MeshCoP::SteeringData leaderSteeringData;
Admitter::Iterator iter;
Admitter::EnrollerInfo enrollerInfo;
Admitter::JoinerInfo joinerInfo;
BitSet<kNumEnrollers> foundEnrollers;
uint16_t sessionId;
uint16_t rloc16;
MeshCoP::UdpEncapsulationTlvHeader udpEncapHeader;
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAdmitterForwardingUdpProxy");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
enrollers[i] = &nexus.CreateNode();
}
nexus.AdvanceTime(0);
// Form the topology:
// - `admitter` forms the network (as leader)
// - All enrollers stay disconnected.
admitter.Form();
nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Mle::Mle>().IsLeader());
for (Node *enroller : enrollers)
{
SuccessOrQuit(enroller->Get<Mac::Mac>().SetPanChannel(admitter.Get<Mac::Mac>().GetPanChannel()));
enroller->Get<Mac::Mac>().SetPanId(admitter.Get<Mac::Mac>().GetPanId());
enroller->Get<ThreadNetif>().Up();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Border Admitter on `admitter`");
admitter.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(admitter.Get<Admitter>().IsEnabled());
VerifyOrQuit(!admitter.Get<Admitter>().IsPrimeAdmitter());
nexus.AdvanceTime(45 * Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<Ip6::Filter>().AddUnsecurePort(admitter.Get<Manager>().GetUdpPort()));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Establish a DTLS connection from all `enrollers` to `admitter`");
sockAddr.SetAddress(admitter.Get<Mle::Mle>().GetLinkLocalAddress());
sockAddr.SetPort(admitter.Get<Manager>().GetUdpPort());
admitter.Get<KeyManager>().GetPskc(pskc);
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Node *enroller = enrollers[i];
SuccessOrQuit(enroller->Get<Tmf::SecureAgent>().SetPsk(pskc.m8, Pskc::kSize));
recvContext[i].Clear();
enroller->Get<Tmf::SecureAgent>().RegisterResourceHandler(HandleResource, &recvContext[i]);
SuccessOrQuit(enroller->Get<Tmf::SecureAgent>().Open(0));
SuccessOrQuit(enroller->Get<Tmf::SecureAgent>().Connect(sockAddr));
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(enroller->Get<Tmf::SecureAgent>().IsConnected());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Prepare mode for each enroller");
modes[0] = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
modes[1] = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
modes[2] = MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx;
modes[3] = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx;
steeringData.SetToPermitAllJoiners();
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Send an `EnrollerRegister` message from all `enrollers`");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
message =
enrollers[i]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister);
VerifyOrQuit(message != nullptr);
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerIdTlv>(*message, kEnrollerIds[i]));
SuccessOrQuit(Tlv::Append<MeshCoP::EnrollerModeTlv>(*message, modes[i]));
SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData));
responseContexts[i].Clear();
SuccessOrQuit(
enrollers[i]->Get<Tmf::SecureAgent>().SendMessage(*message, HandleResponse, &responseContexts[i]));
}
nexus.AdvanceTime(Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check that all registrations were accepted");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
VerifyOrQuit(responseContexts[i].mReceived);
VerifyOrQuit(responseContexts[i].mResponseState == MeshCoP::StateTlv::kAccept);
VerifyOrQuit(responseContexts[i].mHasAdmitterState);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `admitter` becomes active commissioner");
nexus.AdvanceTime(Time::kOneSecondInMsec);
VerifyOrQuit(admitter.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(admitter.Get<Admitter>().IsActiveCommissioner());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindBorderAgentRloc(rloc16));
VerifyOrQuit(rloc16 == admitter.Get<Mle::Mle>().GetRloc16());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindCommissioningSessionId(sessionId));
VerifyOrQuit(sessionId == admitter.Get<Admitter>().GetCommissionerSessionId());
SuccessOrQuit(admitter.Get<NetworkData::Leader>().FindSteeringData(leaderSteeringData));
VerifyOrQuit(leaderSteeringData == steeringData);
VerifyOrQuit(leaderSteeringData.PermitsAllJoiners());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the Enroller info on `admitter`");
foundEnrollers.Clear();
iter.Init(admitter.GetInstance());
while (iter.GetNextEnrollerInfo(enrollerInfo) == kErrorNone)
{
uint8_t matchedIndex;
LogEnroller(enrollerInfo);
matchedIndex = FindMatchingEnroller<kNumEnrollers>(enrollerInfo, kEnrollerIds, foundEnrollers);
VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData);
VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]);
VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound);
}
VerifyOrQuit(DidFindAllEnrollers<kNumEnrollers>(foundEnrollers));
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Prepare a `DiagnosticGetQuery` message");
diagMessage = enrollers[0]->Get<Tmf::Agent>().AllocateAndInitNonConfirmablePostMessage(kUriDiagnosticGetQuery);
VerifyOrQuit(diagMessage != nullptr);
SuccessOrQuit(Tlv::Append<NetDiag::TypeListTlv>(*diagMessage, kDiagTlvs, sizeof(kDiagTlvs)));
diagMessage->WriteMessageId(0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Embed the `DiagnosticGetQuery` into `ProxyTx` message and send it from `enrollers[0]`");
message = enrollers[0]->Get<Tmf::SecureAgent>().AllocateAndInitPriorityNonConfirmablePostMessage(kUriProxyTx);
VerifyOrQuit(message != nullptr);
udpEncapHeader.SetSourcePort(Tmf::kUdpPort);
udpEncapHeader.SetDestinationPort(Tmf::kUdpPort);
SuccessOrQuit(Tlv::AppendTlvHeader(*message, MeshCoP::Tlv::kUdpEncapsulation,
sizeof(udpEncapHeader) + diagMessage->GetLength()));
SuccessOrQuit(message->Append(udpEncapHeader));
SuccessOrQuit(message->AppendBytesFromMessage(*diagMessage, 0, diagMessage->GetLength()));
diagMessage->Free();
SuccessOrQuit(Tlv::Append<MeshCoP::Ip6AddressTlv>(*message, admitter.Get<Mle::Mle>().GetMeshLocalRloc()));
SuccessOrQuit(enrollers[0]->Get<Tmf::SecureAgent>().SendMessage(*message));
nexus.AdvanceTime(Time::kOneSecondInMsec);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate that `admitter` receives the `DiagnosticGetQuery` response");
Log("And that it forwards it as `ProxyRx` messages to all `enrollers` with `kForwardUdpProxyRx` mode flag");
for (uint8_t i = 0; i < kNumEnrollers; i++)
{
Ip6::Address senderAddr;
OffsetRange offsetRange;
message = AsCoapMessagePtr(recvContext[i].mProxyRxMsgs.GetHead());
if ((modes[i] & MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx) == 0)
{
VerifyOrQuit(message == nullptr);
Log(" Enroller %s does not set `kForwardUdpProxyRx` mode - so did not get `ProxyRx`", kEnrollerIds[i]);
continue;
}
VerifyOrQuit(message != nullptr);
VerifyOrQuit(message->ReadType() == Coap::kTypeNonConfirmable);
VerifyOrQuit(message->ReadCode() == Coap::kCodePost);
SuccessOrQuit(Tlv::FindTlvValueOffsetRange(*message, MeshCoP::Tlv::kUdpEncapsulation, offsetRange));
SuccessOrQuit(Tlv::Find<MeshCoP::Ip6AddressTlv>(*message, senderAddr));
VerifyOrQuit(senderAddr == admitter.Get<Mle::Mle>().GetMeshLocalRloc());
Log(" Enroller %s received `ProxyRx` from %s", kEnrollerIds[i], senderAddr.ToString().AsCString());
}
}
//---------------------------------------------------------------------------------------------------------------------
static constexpr uint32_t kInfraIfIndex = 1;
void ValidateAdmitterMdnsService(Node &aNode)
{
static const char kDefaultServiceBaseName[] = OPENTHREAD_CONFIG_BORDER_AGENT_MESHCOP_SERVICE_BASE_NAME;
Dns::Multicast::Core::Iterator *iterator;
Dns::Multicast::Core::Service service;
Dns::Multicast::Core::EntryState entryState;
bool found = false;
iterator = aNode.Get<Dns::Multicast::Core>().AllocateIterator();
VerifyOrQuit(iterator != nullptr);
while (aNode.Get<Dns::Multicast::Core>().GetNextService(*iterator, service, entryState) == kErrorNone)
{
Log("- - - - - - - - - - - - - - - - -");
Log(" HostName: %s", service.mHostName);
Log(" ServiceInstance: %s", service.mServiceInstance);
Log(" ServiceType: %s", service.mServiceType);
if (StringMatch(service.mServiceType, "_meshcop._udp"))
{
for (uint16_t i = 0; i < service.mSubTypeLabelsLength; i++)
{
Log(" SubType: %s", service.mSubTypeLabels[i]);
}
Log(" Port: %u", service.mPort);
Log(" TTL: %lu", ToUlong(service.mTtl));
VerifyOrQuit(StringStartsWith(service.mServiceInstance, kDefaultServiceBaseName));
VerifyOrQuit(StringStartsWith(service.mHostName, "ot"));
VerifyOrQuit(service.mPort == aNode.Get<MeshCoP::BorderAgent::Manager>().GetUdpPort());
VerifyOrQuit(service.mTtl > 0);
VerifyOrQuit(service.mInfraIfIndex == 1);
VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED);
if (aNode.Get<Admitter>().IsPrimeAdmitter())
{
VerifyOrQuit(service.mSubTypeLabelsLength == 1);
VerifyOrQuit(StringMatch(service.mSubTypeLabels[0], "_admitter"));
}
else
{
VerifyOrQuit(service.mSubTypeLabelsLength == 0);
}
found = true;
}
}
VerifyOrQuit(found);
aNode.Get<Dns::Multicast::Core>().FreeIterator(*iterator);
}
//---------------------------------------------------------------------------------------------------------------------
void TestBorderAdmitterDnssdService(void)
{
Core nexus;
Node &node1 = nexus.CreateNode();
Node &node2 = nexus.CreateNode();
Log("------------------------------------------------------------------------------------------------------");
Log("TestBorderAdmitterDnssdService");
nexus.AdvanceTime(0);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Enable mDNS
SuccessOrQuit(node1.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
VerifyOrQuit(node1.Get<Dns::Multicast::Core>().IsEnabled());
SuccessOrQuit(node2.Get<Dns::Multicast::Core>().SetEnabled(true, kInfraIfIndex));
VerifyOrQuit(node2.Get<Dns::Multicast::Core>().IsEnabled());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Form the topology.
node1.Form();
nexus.AdvanceTime(50 * Time::kOneSecondInMsec);
node2.Join(node1);
nexus.AdvanceTime(10 * Time::kOneMinuteInMsec);
VerifyOrQuit(node1.Get<Mle::Mle>().IsLeader());
VerifyOrQuit(node2.Get<Mle::Mle>().IsRouter());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Check Border Admitter initial state");
VerifyOrQuit(!node1.Get<Admitter>().IsEnabled());
VerifyOrQuit(!node2.Get<Admitter>().IsEnabled());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Admitter role on `node1` and validate that it becomes the Prime Admitter");
node1.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(node1.Get<Admitter>().IsEnabled());
nexus.AdvanceTime(45 * Time::kOneSecondInMsec);
VerifyOrQuit(node1.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!node1.Get<Admitter>().IsActiveCommissioner());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the registered mDNS MeshCop service by `node1` including `_admitter` sub-type");
ValidateAdmitterMdnsService(node1);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the registered mDNS MeshCop service by `node2` (should not have `_admitter` sub-type)");
ValidateAdmitterMdnsService(node2);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Enable Admitter role on `node2` and validate that `node1` remains the Prime Admitter");
node2.Get<Admitter>().SetEnabled(true);
VerifyOrQuit(node2.Get<Admitter>().IsEnabled());
nexus.AdvanceTime(45 * Time::kOneSecondInMsec);
VerifyOrQuit(node1.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!node1.Get<Admitter>().IsActiveCommissioner());
VerifyOrQuit(!node2.Get<Admitter>().IsPrimeAdmitter());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the registered mDNS MeshCop service by `node1` including `_admitter` sub-type");
ValidateAdmitterMdnsService(node1);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the registered mDNS MeshCop service by `node2` (should not have `_admitter` sub-type)");
ValidateAdmitterMdnsService(node2);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Disable Admitter role on `node1` and check that `node2` becomes the Prime Admitter");
node1.Get<Admitter>().SetEnabled(false);
VerifyOrQuit(!node1.Get<Admitter>().IsEnabled());
VerifyOrQuit(!node1.Get<Admitter>().IsPrimeAdmitter());
nexus.AdvanceTime(75 * Time::kOneSecondInMsec);
VerifyOrQuit(node2.Get<Admitter>().IsPrimeAdmitter());
VerifyOrQuit(!node2.Get<Admitter>().IsActiveCommissioner());
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the registered mDNS MeshCop service by `node1` (no longer publishing `_admitter` sub-type)");
ValidateAdmitterMdnsService(node1);
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Log("Validate the registered mDNS MeshCop service by `node2` (now should include `_admitter` sub-type)");
ValidateAdmitterMdnsService(node2);
}
} // namespace Nexus
} // namespace ot
int main(void)
{
ot::Nexus::TestBorderAdmitterPrimeSelection();
ot::Nexus::TestBorderAdmitterEnrollerInteraction();
ot::Nexus::TestBorderAdmitterCommissionerConflictAndPetitionerRetry();
ot::Nexus::TestBorderAdmitterMultipleEnrollers();
ot::Nexus::TestBorderAdmitterJoinerEnrollerInteraction();
ot::Nexus::TestBorderAdmitterForwardingUdpProxy();
ot::Nexus::TestBorderAdmitterDnssdService();
printf("\nAll tests passed\n");
return 0;
}