/* * 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 #include #include #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().IsLeader()); VerifyOrQuit(node1.Get().IsRouter()); VerifyOrQuit(node2.Get().IsRouter()); VerifyOrQuit(node3.Get().IsRouter()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Check Border Admitter initial state"); VerifyOrQuit(!node1.Get().IsEnabled()); VerifyOrQuit(!node2.Get().IsEnabled()); VerifyOrQuit(!node3.Get().IsEnabled()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Enable Admitter role on `node1` and validate that it becomes the Prime Admitter"); node1.Get().SetEnabled(true); VerifyOrQuit(node1.Get().IsEnabled()); nexus.AdvanceTime(45 * Time::kOneSecondInMsec); VerifyOrQuit(node1.Get().IsPrimeAdmitter()); VerifyOrQuit(!node1.Get().IsActiveCommissioner()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Validate that the NetworkData contains a single Admitter Service from `node1`"); netDataIter.Reset(); SuccessOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16)); VerifyOrQuit(rloc16 == node1.Get().GetRloc16()); VerifyOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16) == kErrorNotFound); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Enable Admitter role on `node2` & `node3` and validate that `node1` remains the Prime Admitter"); node2.Get().SetEnabled(true); VerifyOrQuit(node2.Get().IsEnabled()); node3.Get().SetEnabled(true); VerifyOrQuit(node3.Get().IsEnabled()); nexus.AdvanceTime(45 * Time::kOneSecondInMsec); VerifyOrQuit(node1.Get().IsPrimeAdmitter()); VerifyOrQuit(!node1.Get().IsActiveCommissioner()); VerifyOrQuit(!node2.Get().IsPrimeAdmitter()); VerifyOrQuit(!node3.Get().IsPrimeAdmitter()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Validate that the NetworkData contains the Admitter Service from `node1`"); netDataIter.Reset(); SuccessOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16)); VerifyOrQuit(rloc16 == node1.Get().GetRloc16()); VerifyOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16) == kErrorNotFound); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Disable Admitter role on `node1` and check that another Prime Admitter is elected"); node1.Get().SetEnabled(false); VerifyOrQuit(!node1.Get().IsEnabled()); VerifyOrQuit(!node1.Get().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().IsPrimeAdmitter() != node3.Get().IsPrimeAdmitter()); VerifyOrQuit(!node2.Get().IsActiveCommissioner()); VerifyOrQuit(!node3.Get().IsActiveCommissioner()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Validate that the NetworkData contains a single Admitter Service entry"); netDataIter.Reset(); SuccessOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16)); VerifyOrQuit(rloc16 == (node2.Get().IsPrimeAdmitter() ? node2.Get().GetRloc16() : node3.Get().GetRloc16())); VerifyOrQuit(netDataIter.GetNextBorderAdmitterInfo(rloc16) == kErrorNotFound); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Disable Admitter role on all nodes (`node2` and `node3`)"); node2.Get().SetEnabled(false); VerifyOrQuit(!node2.Get().IsEnabled()); VerifyOrQuit(!node2.Get().IsPrimeAdmitter()); node3.Get().SetEnabled(false); VerifyOrQuit(!node3.Get().IsEnabled()); VerifyOrQuit(!node3.Get().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().SetEnabled(true); node2.Get().SetEnabled(true); node3.Get().SetEnabled(true); VerifyOrQuit(node1.Get().IsEnabled()); VerifyOrQuit(node2.Get().IsEnabled()); VerifyOrQuit(node3.Get().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().IsPrimeAdmitter()) { VerifyOrQuit(!found); found = true; rloc16 = node->Get().GetRloc16(); } VerifyOrQuit(!node->Get().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(aResponse, mAdmitterState); VerifyOrQuit(error == kErrorNone || error == kErrorNotFound); mHasAdmitterState = (error == kErrorNone); error = Tlv::Find(aResponse, mCommrSessionId); VerifyOrQuit(error == kErrorNone || error == kErrorNotFound); mHasCommrSessionId = (error == kErrorNone); error = Tlv::Find(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 { // 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(aContext); VerifyOrQuit(!responseContext->mReceived); // Duplicate response responseContext->mReceived = true; SuccessOrQuit(Tlv::Find(aMsg->mMessage, responseContext->mResponseState)); responseContext->ParseAdmitterInfo(aMsg->mMessage); Log(" Received response - %s", MeshCoP::StateTlv::StateToString(static_cast(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 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(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(aMsg.mMessage, joinerPort)); SuccessOrQuit(Tlv::Find(aMsg.mMessage, joinerIid)); Log(" Received `RelayRx` from joiner - port:%u iid:%s", joinerPort, joinerIid.ToString().AsCString()); msgClone = aMsg.mMessage.Clone(); VerifyOrQuit(msgClone != nullptr); recvContext->mRelayRxMsgs.Enqueue(*msgClone); break; case kUriProxyRx: Log(" Received `ProxyRx`"); msgClone = aMsg.mMessage.Clone(); 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().IsLeader()); SuccessOrQuit(enroller.Get().SetPanChannel(admitter.Get().GetPanChannel())); enroller.Get().SetPanId(admitter.Get().GetPanId()); enroller.Get().Up(); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Enable Border Admitter on `admitter`"); admitter.Get().SetEnabled(true); VerifyOrQuit(admitter.Get().IsEnabled()); VerifyOrQuit(!admitter.Get().IsPrimeAdmitter()); nexus.AdvanceTime(45 * Time::kOneSecondInMsec); VerifyOrQuit(admitter.Get().IsPrimeAdmitter()); VerifyOrQuit(!admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().AddUnsecurePort(admitter.Get().GetUdpPort())); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Establish a DTLS connection from `enroller` to `admitter`"); sockAddr.SetAddress(admitter.Get().GetLinkLocalAddress()); sockAddr.SetPort(admitter.Get().GetUdpPort()); admitter.Get().GetPskc(pskc); SuccessOrQuit(enroller.Get().SetPsk(pskc.m8, Pskc::kSize)); enroller.Get().RegisterResourceHandler(HandleResource, &recvContext); SuccessOrQuit(enroller.Get().Open(0)); SuccessOrQuit(enroller.Get().Connect(sockAddr)); nexus.AdvanceTime(Time::kOneSecondInMsec); VerifyOrQuit(enroller.Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); mode = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx; steeringData.SetToPermitAllJoiners(); SuccessOrQuit(Tlv::Append(*message, kEnrollerId)); SuccessOrQuit(Tlv::Append(*message, mode)); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); responseContext.Clear(); SuccessOrQuit(enroller.Get().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().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().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().FindBorderAgentRloc(rloc16)); VerifyOrQuit(rloc16 == admitter.Get().GetRloc16()); SuccessOrQuit(admitter.Get().FindCommissioningSessionId(sessionId)); VerifyOrQuit(sessionId == admitter.Get().GetCommissionerSessionId()); SuccessOrQuit(admitter.Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); responseContext.Clear(); SuccessOrQuit(enroller.Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); mode = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx; SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); SuccessOrQuit(Tlv::Append(*message, mode)); responseContext.Clear(); SuccessOrQuit(enroller.Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Send an `EnrollerKeepAlive` message with Steering Data TLV"); message = enroller.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(steeringData.Init(MeshCoP::SteeringData::kMaxLength)); SuccessOrQuit(steeringData.UpdateBloomFilter(admitter.Get().GetExtAddress())); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); responseContext.Clear(); SuccessOrQuit(enroller.Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().FindBorderAgentRloc(rloc16)); VerifyOrQuit(rloc16 == admitter.Get().GetRloc16()); SuccessOrQuit(admitter.Get().FindCommissioningSessionId(sessionId)); VerifyOrQuit(sessionId == admitter.Get().GetCommissionerSessionId()); SuccessOrQuit(admitter.Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().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().IsPrimeAdmitter()); VerifyOrQuit(!admitter.Get().IsActiveCommissioner()); VerifyOrQuit(!admitter.Get().HasBorderAgentRloc()); nexus.AdvanceTime(10 * Time::kOneSecondInMsec); VerifyOrQuit(!enroller.Get().IsConnected()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Re-establish DTLS session"); SuccessOrQuit(enroller.Get().Connect(sockAddr)); nexus.AdvanceTime(Time::kOneSecondInMsec); VerifyOrQuit(enroller.Get().IsConnected()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Register as enroller again"); message = enroller.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, kEnrollerId)); SuccessOrQuit(Tlv::Append(*message, mode)); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); responseContext.Clear(); SuccessOrQuit(enroller.Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().FindBorderAgentRloc(rloc16)); VerifyOrQuit(rloc16 == admitter.Get().GetRloc16()); SuccessOrQuit(admitter.Get().FindCommissioningSessionId(sessionId)); VerifyOrQuit(sessionId == admitter.Get().GetCommissionerSessionId()); SuccessOrQuit(admitter.Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); message = enroller.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kReject)); responseContext.Clear(); SuccessOrQuit(enroller.Get().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().IsPrimeAdmitter()); VerifyOrQuit(!admitter.Get().IsActiveCommissioner()); VerifyOrQuit(enroller.Get().IsConnected()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Register as enroller again"); message = enroller.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, kEnrollerId)); SuccessOrQuit(Tlv::Append(*message, mode)); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); responseContext.Clear(); SuccessOrQuit(enroller.Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Send an `EnrollerRegister` message while already registered, with different parameters"); message = enroller.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); mode = 0; SuccessOrQuit(steeringData.Init(8)); SuccessOrQuit(steeringData.UpdateBloomFilter(admitter.Get().GetExtAddress())); SuccessOrQuit(Tlv::Append(*message, kEnrollerIdAlt)); SuccessOrQuit(Tlv::Append(*message, mode)); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); responseContext.Clear(); SuccessOrQuit(enroller.Get().SendMessage(*message, HandleResponse, &responseContext)); nexus.AdvanceTime(Time::kOneSecondInMsec); VerifyOrQuit(responseContext.mReceived); VerifyOrQuit(responseContext.mResponseState == MeshCoP::StateTlv::kAccept); VerifyOrQuit(admitter.Get().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().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().FindBorderAgentRloc(rloc16)); VerifyOrQuit(rloc16 == admitter.Get().GetRloc16()); SuccessOrQuit(admitter.Get().FindCommissioningSessionId(sessionId)); VerifyOrQuit(sessionId == admitter.Get().GetCommissionerSessionId()); SuccessOrQuit(admitter.Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); responseContext.Clear(); SuccessOrQuit(enroller.Get().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().IsConnected()); VerifyOrQuit(admitter.Get().IsPrimeAdmitter()); VerifyOrQuit(!admitter.Get().IsActiveCommissioner()); VerifyOrQuit(!admitter.Get().HasBorderAgentRloc()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Re-establish DTLS session"); SuccessOrQuit(enroller.Get().Connect(sockAddr)); nexus.AdvanceTime(Time::kOneSecondInMsec); VerifyOrQuit(enroller.Get().IsConnected()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Send `EnrollerRegister` with missing TLVs, validate that it is rejected"); for (uint16_t testIter = 0; testIter < 3; testIter++) { message = enroller.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); // Skip one of the required TLVs for each `testIter`. if (testIter != 0) { SuccessOrQuit(Tlv::Append(*message, kEnrollerId)); } if (testIter != 1) { SuccessOrQuit(Tlv::Append(*message, mode)); } if (testIter != 2) { SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); } responseContext.Clear(); SuccessOrQuit(enroller.Get().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().GetExtAddress())); message = enroller.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, kEnrollerId)); SuccessOrQuit(Tlv::Append(*message, mode)); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); responseContext.Clear(); SuccessOrQuit(enroller.Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, kEnrollerId)); SuccessOrQuit(Tlv::Append(*message, mode)); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); responseContext.Clear(); SuccessOrQuit(enroller.Get().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().IsLeader()); VerifyOrQuit(otherCommr.Get().IsRouter()); SuccessOrQuit(enroller.Get().SetPanChannel(admitter.Get().GetPanChannel())); enroller.Get().SetPanId(admitter.Get().GetPanId()); enroller.Get().Up(); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Enable Border Admitter on `admitter`"); admitter.Get().SetEnabled(true); VerifyOrQuit(admitter.Get().IsEnabled()); VerifyOrQuit(!admitter.Get().IsPrimeAdmitter()); nexus.AdvanceTime(45 * Time::kOneSecondInMsec); VerifyOrQuit(admitter.Get().IsPrimeAdmitter()); VerifyOrQuit(!admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().AddUnsecurePort(admitter.Get().GetUdpPort())); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Make `otherCommr` the active commissioner"); SuccessOrQuit(otherCommr.Get().Start(nullptr, nullptr, nullptr)); nexus.AdvanceTime(2 * Time::kOneSecondInMsec); VerifyOrQuit(otherCommr.Get().GetState() == Commissioner::kStateActive); SuccessOrQuit(admitter.Get().FindBorderAgentRloc(rloc16)); VerifyOrQuit(rloc16 == otherCommr.Get().GetRloc16()); nexus.AdvanceTime(5 * Time::kOneSecondInMsec); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Establish a DTLS connection from `enroller` to `admitter`"); sockAddr.SetAddress(admitter.Get().GetLinkLocalAddress()); sockAddr.SetPort(admitter.Get().GetUdpPort()); admitter.Get().GetPskc(pskc); SuccessOrQuit(enroller.Get().SetPsk(pskc.m8, Pskc::kSize)); enroller.Get().RegisterResourceHandler(HandleResource, &recvContext); SuccessOrQuit(enroller.Get().Open(0)); SuccessOrQuit(enroller.Get().Connect(sockAddr)); nexus.AdvanceTime(Time::kOneSecondInMsec); VerifyOrQuit(enroller.Get().IsConnected()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Send an `EnrollerRegister` message from `enroller` to `admitter`"); message = enroller.Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); mode = MeshCoP::EnrollerModeTlv::kForwardJoinerRelayRx | MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx; steeringData.SetToPermitAllJoiners(); SuccessOrQuit(Tlv::Append(*message, kEnrollerId)); SuccessOrQuit(Tlv::Append(*message, mode)); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); SuccessOrQuit(enroller.Get().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().IsPrimeAdmitter()); VerifyOrQuit(!admitter.Get().IsActiveCommissioner()); VerifyOrQuit(admitter.Get().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().IsPrimeAdmitter()); VerifyOrQuit(!admitter.Get().IsActiveCommissioner()); VerifyOrQuit(admitter.Get().IsPetitionRejected()); VerifyOrQuit(otherCommr.Get().GetState() == Commissioner::kStateActive); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Stop `otherCommr` from acting as active commissioner"); recvContext.Clear(); SuccessOrQuit(otherCommr.Get().Stop()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Validate that the `admitter` will detect this, petitions, and becomes active commissioner"); nexus.AdvanceTime(2 * Time::kOneSecondInMsec); VerifyOrQuit(admitter.Get().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); VerifyOrQuit(!admitter.Get().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().FindBorderAgentRloc(rloc16)); VerifyOrQuit(rloc16 == admitter.Get().GetRloc16()); SuccessOrQuit(admitter.Get().FindCommissioningSessionId(sessionId)); VerifyOrQuit(sessionId == admitter.Get().GetCommissionerSessionId()); SuccessOrQuit(admitter.Get().FindSteeringData(leaderSteeringData)); VerifyOrQuit(leaderSteeringData == steeringData); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("From `otherCommr` forcefully evict the current active commissioner (`admitter`)"); recvContext.Clear(); SuccessOrQuit(otherCommr.Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); SuccessOrQuit(enroller.Get().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 FindMatchingEnroller(const Admitter::EnrollerInfo &aInfo, const char *(&aEnrollerIds)[kNumEnrollers], BitSet &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 bool DidFindAllEnrollers(const BitSet &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 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().IsLeader()); for (Node *enroller : enrollers) { SuccessOrQuit(enroller->Get().SetPanChannel(admitter.Get().GetPanChannel())); enroller->Get().SetPanId(admitter.Get().GetPanId()); enroller->Get().Up(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Enable Border Admitter on `admitter`"); admitter.Get().SetEnabled(true); VerifyOrQuit(admitter.Get().IsEnabled()); VerifyOrQuit(!admitter.Get().IsPrimeAdmitter()); nexus.AdvanceTime(45 * Time::kOneSecondInMsec); VerifyOrQuit(admitter.Get().IsPrimeAdmitter()); VerifyOrQuit(!admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().AddUnsecurePort(admitter.Get().GetUdpPort())); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Establish a DTLS connection from all `enrollers` to `admitter`"); sockAddr.SetAddress(admitter.Get().GetLinkLocalAddress()); sockAddr.SetPort(admitter.Get().GetUdpPort()); admitter.Get().GetPskc(pskc); for (uint8_t i = 0; i < kNumEnrollers; i++) { Node *enroller = enrollers[i]; SuccessOrQuit(enroller->Get().SetPsk(pskc.m8, Pskc::kSize)); recvContext[i].Clear(); enroller->Get().RegisterResourceHandler(HandleResource, &recvContext[i]); SuccessOrQuit(enroller->Get().Open(0)); SuccessOrQuit(enroller->Get().Connect(sockAddr)); nexus.AdvanceTime(Time::kOneSecondInMsec); VerifyOrQuit(enroller->Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, kEnrollerIds[i])); SuccessOrQuit(Tlv::Append(*message, mode)); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData[i])); responseContexts[i].Clear(); SuccessOrQuit( enrollers[i]->Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().FindBorderAgentRloc(rloc16)); VerifyOrQuit(rloc16 == admitter.Get().GetRloc16()); SuccessOrQuit(admitter.Get().FindCommissioningSessionId(sessionId)); VerifyOrQuit(sessionId == admitter.Get().GetCommissionerSessionId()); SuccessOrQuit(admitter.Get().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(enrollerInfo, kEnrollerIds, foundEnrollers); VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData[matchedIndex]); VerifyOrQuit(enrollerInfo.mMode == mode); VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound); } VerifyOrQuit(DidFindAllEnrollers(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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kReject)); responseContexts[0].Clear(); SuccessOrQuit(enrollers[0]->Get().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(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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().FindBorderAgentRloc(rloc16)); VerifyOrQuit(rloc16 == admitter.Get().GetRloc16()); SuccessOrQuit(admitter.Get().FindCommissioningSessionId(sessionId)); VerifyOrQuit(sessionId == admitter.Get().GetCommissionerSessionId()); combinedSteeringData = steeringData[1]; SuccessOrQuit(combinedSteeringData.MergeBloomFilterWith(steeringData[2])); SuccessOrQuit(combinedSteeringData.MergeBloomFilterWith(steeringData[3])); SuccessOrQuit(admitter.Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); responseContexts[i].Clear(); SuccessOrQuit( enrollers[i]->Get().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(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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); combinedSteeringData = steeringData[2]; SuccessOrQuit(combinedSteeringData.MergeBloomFilterWith(steeringData[3])); SuccessOrQuit(admitter.Get().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 foundEnrollers; BitSet 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().IsLeader()); for (Node *enroller : enrollers) { SuccessOrQuit(enroller->Get().SetPanChannel(admitter.Get().GetPanChannel())); enroller->Get().SetPanId(admitter.Get().GetPanId()); enroller->Get().Up(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Enable Border Admitter on `admitter`"); admitter.Get().SetEnabled(true); VerifyOrQuit(admitter.Get().IsEnabled()); VerifyOrQuit(!admitter.Get().IsPrimeAdmitter()); nexus.AdvanceTime(45 * Time::kOneSecondInMsec); VerifyOrQuit(admitter.Get().IsPrimeAdmitter()); VerifyOrQuit(!admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().AddUnsecurePort(admitter.Get().GetUdpPort())); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Establish a DTLS connection from all `enrollers` to `admitter`"); sockAddr.SetAddress(admitter.Get().GetLinkLocalAddress()); sockAddr.SetPort(admitter.Get().GetUdpPort()); admitter.Get().GetPskc(pskc); for (uint8_t i = 0; i < kNumEnrollers; i++) { Node *enroller = enrollers[i]; SuccessOrQuit(enroller->Get().SetPsk(pskc.m8, Pskc::kSize)); recvContext[i].Clear(); enroller->Get().RegisterResourceHandler(HandleResource, &recvContext[i]); SuccessOrQuit(enroller->Get().Open(0)); SuccessOrQuit(enroller->Get().Connect(sockAddr)); nexus.AdvanceTime(Time::kOneSecondInMsec); VerifyOrQuit(enroller->Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, kEnrollerIds[i])); SuccessOrQuit(Tlv::Append(*message, modes[i])); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); responseContexts[i].Clear(); SuccessOrQuit( enrollers[i]->Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().FindBorderAgentRloc(rloc16)); VerifyOrQuit(rloc16 == admitter.Get().GetRloc16()); SuccessOrQuit(admitter.Get().FindCommissioningSessionId(sessionId)); VerifyOrQuit(sessionId == admitter.Get().GetCommissionerSessionId()); SuccessOrQuit(admitter.Get().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(enrollerInfo, kEnrollerIds, foundEnrollers); VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData); VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]); VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound); } VerifyOrQuit(DidFindAllEnrollers(foundEnrollers)); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Start `joiners[0]`"); joiners[0]->Get().Up(); SuccessOrQuit(joiners[0]->Get().Start(kPskd, /* aProvisioningUrl */ nullptr, /* aVendorName */ nullptr, /* aVendorModel */ nullptr, /* aVendorSwVersion */ nullptr, /* aVendorData */ nullptr, /* aCallback */ nullptr, /* aContext */ nullptr)); joinerIids[0].InitFromExtAddress(joiners[0]->Get().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(*message, readIid)); SuccessOrQuit(Tlv::Find(*message, joinerRouterRloc)); VerifyOrQuit(readIid == joinerIids[0]); VerifyOrQuit(joinerRouterRloc == admitter.Get().GetRloc16()); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Stop `joiners[0]`"); joiners[0]->Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerAccept); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, joinerIids[0])); responseContexts[0].Clear(); SuccessOrQuit(enrollers[0]->Get().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(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(foundEnrollers)); nexus.AdvanceTime(Time::kOneSecondInMsec); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Start `joiners[0]` again and validate that its `RelayRx` are only forwarded to `enrollers[0]`"); joiners[0]->Get().Up(); SuccessOrQuit(joiners[0]->Get().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(*message, readIid)); SuccessOrQuit(Tlv::Find(*message, joinerRouterRloc)); VerifyOrQuit(readIid == joinerIids[0]); VerifyOrQuit(joinerRouterRloc == admitter.Get().GetRloc16()); } joiners[0]->Get().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().Up(); SuccessOrQuit(joiners[1]->Get().Start(kPskd, /* aProvisioningUrl */ nullptr, /* aVendorName */ nullptr, /* aVendorModel */ nullptr, /* aVendorSwVersion */ nullptr, /* aVendorData */ nullptr, /* aCallback */ nullptr, /* aContext */ nullptr)); joinerIids[1].InitFromExtAddress(joiners[1]->Get().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(*message, readIid)); SuccessOrQuit(Tlv::Find(*message, joinerRouterRloc)); VerifyOrQuit(readIid == joinerIids[1]); VerifyOrQuit(joinerRouterRloc == admitter.Get().GetRloc16()); } joiners[1]->Get().Stop(); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Send `EnrollerKeepAlive` message from `enrollers[0]` change its mode to disallow `kForwardJoinerRelayRx`"); modes[0] = MeshCoP::EnrollerModeTlv::kForwardUdpProxyRx; message = enrollers[0]->Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); SuccessOrQuit(Tlv::Append(*message, modes[0])); responseContexts[0].Clear(); SuccessOrQuit(enrollers[0]->Get().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(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(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().Up(); SuccessOrQuit(joiners[0]->Get().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(*message, readIid)); SuccessOrQuit(Tlv::Find(*message, joinerRouterRloc)); VerifyOrQuit(readIid == joinerIids[0]); VerifyOrQuit(joinerRouterRloc == admitter.Get().GetRloc16()); } joiners[0]->Get().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().Up(); SuccessOrQuit(joiners[1]->Get().Start(kPskd, /* aProvisioningUrl */ nullptr, /* aVendorName */ nullptr, /* aVendorModel */ nullptr, /* aVendorSwVersion */ nullptr, /* aVendorData */ nullptr, /* aCallback */ nullptr, /* aContext */ nullptr)); joinerIids[1].InitFromExtAddress(joiners[1]->Get().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(*message, readIid)); SuccessOrQuit(Tlv::Find(*message, joinerRouterRloc)); VerifyOrQuit(readIid == joinerIids[1]); VerifyOrQuit(joinerRouterRloc == admitter.Get().GetRloc16()); } joiners[1]->Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); SuccessOrQuit(Tlv::Append(*message, modes[0])); responseContexts[0].Clear(); SuccessOrQuit(enrollers[0]->Get().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(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(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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); responseContexts[i].Clear(); SuccessOrQuit( enrollers[i]->Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerAccept); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, joinerIids[1])); responseContexts[0].Clear(); SuccessOrQuit(enrollers[0]->Get().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(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(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().Up(); SuccessOrQuit(joiners[1]->Get().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(*message, readIid)); SuccessOrQuit(Tlv::Find(*message, joinerRouterRloc)); VerifyOrQuit(readIid == joinerIids[1]); VerifyOrQuit(joinerRouterRloc == admitter.Get().GetRloc16()); } joiners[1]->Get().Stop(); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("From `enrollers[1]` send `EnrollerJoinerAccept` for `joiners[1]`"); message = enrollers[1]->Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerAccept); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, joinerIids[1])); responseContexts[1].Clear(); SuccessOrQuit(enrollers[1]->Get().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(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().Up(); SuccessOrQuit(joiners[1]->Get().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(*message, readIid)); SuccessOrQuit(Tlv::Find(*message, joinerRouterRloc)); VerifyOrQuit(readIid == joinerIids[1]); VerifyOrQuit(joinerRouterRloc == admitter.Get().GetRloc16()); } joiners[1]->Get().Stop(); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Send an `EnrollerJoinerAccept` message again accepting `joiners[1]` from `enrollers[0]`"); message = enrollers[0]->Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerAccept); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, joinerIids[1])); responseContexts[0].Clear(); SuccessOrQuit(enrollers[0]->Get().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(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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerRelease); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, joinerIids[0])); responseContexts[0].Clear(); SuccessOrQuit(enrollers[0]->Get().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(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(foundEnrollers)); nexus.AdvanceTime(Time::kOneSecondInMsec); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Send an `EnrollerJoinerRelease` message again releasing `joiners[0]` from `enrollers[0]`"); message = enrollers[0]->Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerRelease); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, joinerIids[0])); responseContexts[0].Clear(); SuccessOrQuit(enrollers[0]->Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerRelease); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, joinerIids[1])); responseContexts[0].Clear(); SuccessOrQuit(enrollers[0]->Get().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().AllocateAndInitPriorityConfirmablePostMessage( kUriEnrollerJoinerAccept); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, joinerIids[j])); responseContexts[2].Clear(); SuccessOrQuit( enrollers[2]->Get().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(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(foundEnrollers)); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Send an `EnrollerJoinerRelease` message from `enrollers[2]` with wildcard IID releasing all joiners"); message = enrollers[2]->Get().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerRelease); VerifyOrQuit(message != nullptr); wildcardJoinerIid.Clear(); SuccessOrQuit(Tlv::Append(*message, wildcardJoinerIid)); responseContexts[2].Clear(); SuccessOrQuit(enrollers[2]->Get().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(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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerJoinerAccept); VerifyOrQuit(message != nullptr); wildcardJoinerIid.Clear(); SuccessOrQuit(Tlv::Append(*message, wildcardJoinerIid)); responseContexts[2].Clear(); SuccessOrQuit(enrollers[2]->Get().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().AllocateAndInitPriorityConfirmablePostMessage( kUriEnrollerJoinerAccept); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, joinerIids[j])); responseContexts[2].Clear(); SuccessOrQuit( enrollers[2]->Get().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(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(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().AllocateAndInitPriorityConfirmablePostMessage( kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); responseContexts[i].Clear(); SuccessOrQuit( enrollers[i]->Get().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(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(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().Up(); SuccessOrQuit(joiners[1]->Get().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(*message, readIid)); SuccessOrQuit(Tlv::Find(*message, joinerRouterRloc)); VerifyOrQuit(readIid == joinerIids[1]); VerifyOrQuit(joinerRouterRloc == admitter.Get().GetRloc16()); } joiners[1]->Get().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(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(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().AllocateAndInitPriorityConfirmablePostMessage( kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); responseContexts[i].Clear(); SuccessOrQuit( enrollers[i]->Get().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(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().AllocateAndInitPriorityConfirmablePostMessage( kUriEnrollerKeepAlive); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, MeshCoP::StateTlv::kAccept)); responseContexts[i].Clear(); SuccessOrQuit( enrollers[i]->Get().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(enrollerInfo, kEnrollerIds, foundEnrollers); VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData); VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]); VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound); } VerifyOrQuit(DidFindAllEnrollers(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 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().IsLeader()); for (Node *enroller : enrollers) { SuccessOrQuit(enroller->Get().SetPanChannel(admitter.Get().GetPanChannel())); enroller->Get().SetPanId(admitter.Get().GetPanId()); enroller->Get().Up(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Enable Border Admitter on `admitter`"); admitter.Get().SetEnabled(true); VerifyOrQuit(admitter.Get().IsEnabled()); VerifyOrQuit(!admitter.Get().IsPrimeAdmitter()); nexus.AdvanceTime(45 * Time::kOneSecondInMsec); VerifyOrQuit(admitter.Get().IsPrimeAdmitter()); VerifyOrQuit(!admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().AddUnsecurePort(admitter.Get().GetUdpPort())); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Establish a DTLS connection from all `enrollers` to `admitter`"); sockAddr.SetAddress(admitter.Get().GetLinkLocalAddress()); sockAddr.SetPort(admitter.Get().GetUdpPort()); admitter.Get().GetPskc(pskc); for (uint8_t i = 0; i < kNumEnrollers; i++) { Node *enroller = enrollers[i]; SuccessOrQuit(enroller->Get().SetPsk(pskc.m8, Pskc::kSize)); recvContext[i].Clear(); enroller->Get().RegisterResourceHandler(HandleResource, &recvContext[i]); SuccessOrQuit(enroller->Get().Open(0)); SuccessOrQuit(enroller->Get().Connect(sockAddr)); nexus.AdvanceTime(Time::kOneSecondInMsec); VerifyOrQuit(enroller->Get().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().AllocateAndInitPriorityConfirmablePostMessage(kUriEnrollerRegister); VerifyOrQuit(message != nullptr); SuccessOrQuit(Tlv::Append(*message, kEnrollerIds[i])); SuccessOrQuit(Tlv::Append(*message, modes[i])); SuccessOrQuit(MeshCoP::SteeringDataTlv::AppendTo(*message, steeringData)); responseContexts[i].Clear(); SuccessOrQuit( enrollers[i]->Get().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().IsPrimeAdmitter()); VerifyOrQuit(admitter.Get().IsActiveCommissioner()); SuccessOrQuit(admitter.Get().FindBorderAgentRloc(rloc16)); VerifyOrQuit(rloc16 == admitter.Get().GetRloc16()); SuccessOrQuit(admitter.Get().FindCommissioningSessionId(sessionId)); VerifyOrQuit(sessionId == admitter.Get().GetCommissionerSessionId()); SuccessOrQuit(admitter.Get().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(enrollerInfo, kEnrollerIds, foundEnrollers); VerifyOrQuit(AsCoreType(&enrollerInfo.mSteeringData) == steeringData); VerifyOrQuit(enrollerInfo.mMode == modes[matchedIndex]); VerifyOrQuit(iter.GetNextJoinerInfo(joinerInfo) == kErrorNotFound); } VerifyOrQuit(DidFindAllEnrollers(foundEnrollers)); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Prepare a `DiagnosticGetQuery` message"); diagMessage = enrollers[0]->Get().AllocateAndInitNonConfirmablePostMessage(kUriDiagnosticGetQuery); VerifyOrQuit(diagMessage != nullptr); SuccessOrQuit(Tlv::Append(*diagMessage, kDiagTlvs, sizeof(kDiagTlvs))); diagMessage->WriteMessageId(0); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Embed the `DiagnosticGetQuery` into `ProxyTx` message and send it from `enrollers[0]`"); message = enrollers[0]->Get().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(*message, admitter.Get().GetMeshLocalRloc())); SuccessOrQuit(enrollers[0]->Get().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(*message, senderAddr)); VerifyOrQuit(senderAddr == admitter.Get().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().AllocateIterator(); VerifyOrQuit(iterator != nullptr); while (aNode.Get().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().GetUdpPort()); VerifyOrQuit(service.mTtl > 0); VerifyOrQuit(service.mInfraIfIndex == 1); VerifyOrQuit(entryState == OT_MDNS_ENTRY_STATE_REGISTERED); if (aNode.Get().IsPrimeAdmitter()) { VerifyOrQuit(service.mSubTypeLabelsLength == 1); VerifyOrQuit(StringMatch(service.mSubTypeLabels[0], "_admitter")); } else { VerifyOrQuit(service.mSubTypeLabelsLength == 0); } found = true; } } VerifyOrQuit(found); aNode.Get().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().SetEnabled(true, kInfraIfIndex)); VerifyOrQuit(node1.Get().IsEnabled()); SuccessOrQuit(node2.Get().SetEnabled(true, kInfraIfIndex)); VerifyOrQuit(node2.Get().IsEnabled()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Form the topology. node1.Form(); nexus.AdvanceTime(50 * Time::kOneSecondInMsec); node2.Join(node1); nexus.AdvanceTime(10 * Time::kOneMinuteInMsec); VerifyOrQuit(node1.Get().IsLeader()); VerifyOrQuit(node2.Get().IsRouter()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Check Border Admitter initial state"); VerifyOrQuit(!node1.Get().IsEnabled()); VerifyOrQuit(!node2.Get().IsEnabled()); // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Log("Enable Admitter role on `node1` and validate that it becomes the Prime Admitter"); node1.Get().SetEnabled(true); VerifyOrQuit(node1.Get().IsEnabled()); nexus.AdvanceTime(45 * Time::kOneSecondInMsec); VerifyOrQuit(node1.Get().IsPrimeAdmitter()); VerifyOrQuit(!node1.Get().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().SetEnabled(true); VerifyOrQuit(node2.Get().IsEnabled()); nexus.AdvanceTime(45 * Time::kOneSecondInMsec); VerifyOrQuit(node1.Get().IsPrimeAdmitter()); VerifyOrQuit(!node1.Get().IsActiveCommissioner()); VerifyOrQuit(!node2.Get().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().SetEnabled(false); VerifyOrQuit(!node1.Get().IsEnabled()); VerifyOrQuit(!node1.Get().IsPrimeAdmitter()); nexus.AdvanceTime(75 * Time::kOneSecondInMsec); VerifyOrQuit(node2.Get().IsPrimeAdmitter()); VerifyOrQuit(!node2.Get().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; }