mirror of
https://github.com/espressif/openthread.git
synced 2026-08-15 07:07:46 +00:00
[joiner] introduce Seeker to handle discovery and candidate tracking (#12325)
This change introduces a new `Seeker` class to encapsulate the logic for discovering and prioritizing Joiner Router candidates. The `Joiner` class is updated to use the `Seeker` to perform the discovery scan. The `Joiner` provides a callback `EvaluateScanResult` to the `Seeker` to filter and evaluate scan results based on Steering Data, preserving the existing behavior. This change separates the discovery mechanism from the `Joiner` state machine. This simplifies the `Joiner` implementation and facilitates future enhancements to the joining process. This commit makes to changes to how the Joiner Router candidates are tracked or prioritized.
This commit is contained in:
@@ -586,6 +586,8 @@ openthread_core_files = [
|
||||
"meshcop/panid_query_client.hpp",
|
||||
"meshcop/secure_transport.cpp",
|
||||
"meshcop/secure_transport.hpp",
|
||||
"meshcop/seeker.cpp",
|
||||
"meshcop/seeker.hpp",
|
||||
"meshcop/steering_data.cpp",
|
||||
"meshcop/steering_data.hpp",
|
||||
"meshcop/tcat_agent.cpp",
|
||||
|
||||
@@ -182,6 +182,7 @@ set(COMMON_SOURCES
|
||||
meshcop/network_name.cpp
|
||||
meshcop/panid_query_client.cpp
|
||||
meshcop/secure_transport.cpp
|
||||
meshcop/seeker.cpp
|
||||
meshcop/steering_data.cpp
|
||||
meshcop/tcat_agent.cpp
|
||||
meshcop/timestamp.cpp
|
||||
|
||||
@@ -188,6 +188,7 @@ Instance::Instance(void)
|
||||
, mTmfSecureAgent(*this)
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_JOINER_ENABLE
|
||||
, mSeeker(*this)
|
||||
, mJoiner(*this)
|
||||
#endif
|
||||
#if OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE
|
||||
|
||||
@@ -99,6 +99,7 @@
|
||||
#include "meshcop/joiner_router.hpp"
|
||||
#include "meshcop/meshcop_leader.hpp"
|
||||
#include "meshcop/network_identity.hpp"
|
||||
#include "meshcop/seeker.hpp"
|
||||
#include "net/dhcp6_client.hpp"
|
||||
#include "net/dhcp6_server.hpp"
|
||||
#include "net/dhcp6_types.hpp"
|
||||
@@ -618,6 +619,7 @@ private:
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_JOINER_ENABLE
|
||||
MeshCoP::Seeker mSeeker;
|
||||
MeshCoP::Joiner mJoiner;
|
||||
#endif
|
||||
|
||||
@@ -958,6 +960,8 @@ template <> inline Dnssd &Instance::Get(void) { return mDnssd; }
|
||||
#endif
|
||||
|
||||
#if OPENTHREAD_CONFIG_JOINER_ENABLE
|
||||
template <> inline MeshCoP::Seeker &Instance::Get(void) { return mSeeker; }
|
||||
|
||||
template <> inline MeshCoP::Joiner &Instance::Get(void) { return mJoiner; }
|
||||
#endif
|
||||
|
||||
|
||||
+72
-147
@@ -45,13 +45,11 @@ RegisterLogModule("Joiner");
|
||||
Joiner::Joiner(Instance &aInstance)
|
||||
: InstanceLocator(aInstance)
|
||||
, mState(kStateIdle)
|
||||
, mJoinerRouterIndex(0)
|
||||
, mFinalizeMessage(nullptr)
|
||||
, mTimer(aInstance)
|
||||
{
|
||||
SetIdFromIeeeEui64();
|
||||
mDiscerner.Clear();
|
||||
ClearAllBytes(mJoinerRouters);
|
||||
}
|
||||
|
||||
void Joiner::SetIdFromIeeeEui64(void)
|
||||
@@ -113,10 +111,9 @@ Error Joiner::Start(const char *aPskd,
|
||||
otJoinerCallback aCallback,
|
||||
void *aContext)
|
||||
{
|
||||
Error error;
|
||||
JoinerPskd joinerPskd;
|
||||
Mac::ExtAddress randomAddress;
|
||||
SteeringData::HashBitIndexes filterIndexes;
|
||||
Error error;
|
||||
bool shouldCleanup = false;
|
||||
JoinerPskd joinerPskd;
|
||||
|
||||
LogInfo("Joiner starting");
|
||||
|
||||
@@ -124,51 +121,36 @@ Error Joiner::Start(const char *aPskd,
|
||||
SuccessOrExit(error = Tlv::ValidateStringValue<VendorNameTlv>(aVendorName));
|
||||
SuccessOrExit(error = Tlv::ValidateStringValue<VendorModelTlv>(aVendorModel));
|
||||
SuccessOrExit(error = Tlv::ValidateStringValue<VendorSwVersionTlv>(aVendorSwVersion));
|
||||
|
||||
VerifyOrExit(mState == kStateIdle, error = kErrorBusy);
|
||||
VerifyOrExit(Get<ThreadNetif>().IsUp() && Get<Mle::Mle>().GetRole() == Mle::kRoleDisabled,
|
||||
error = kErrorInvalidState);
|
||||
|
||||
SuccessOrExit(error = joinerPskd.SetFrom(aPskd));
|
||||
|
||||
randomAddress.GenerateRandom();
|
||||
Get<Mac::Mac>().SetExtAddress(randomAddress);
|
||||
Get<Mle::Mle>().UpdateLinkLocalAddress();
|
||||
VerifyOrExit(mState == kStateIdle, error = kErrorBusy);
|
||||
VerifyOrExit(!Get<Seeker>().IsRunning(), error = kErrorBusy);
|
||||
|
||||
SuccessOrExit(error = Get<Tmf::SecureAgent>().Open(Ip6::NetifIdentifier::kNetifThreadInternal));
|
||||
SuccessOrExit(error = Get<Tmf::SecureAgent>().Bind(kJoinerUdpPort));
|
||||
|
||||
// After this, if any of the steps fails, we need to cleanup
|
||||
// (free allocated message, stop seeker, close agent, etc).
|
||||
shouldCleanup = true;
|
||||
|
||||
SuccessOrExit(error = Get<Tmf::SecureAgent>().Bind(Seeker::kUdpPort));
|
||||
Get<Tmf::SecureAgent>().SetConnectCallback(HandleSecureCoapClientConnect, this);
|
||||
Get<Tmf::SecureAgent>().SetPsk(joinerPskd);
|
||||
|
||||
for (JoinerRouter &router : mJoinerRouters)
|
||||
{
|
||||
router.mPriority = 0; // Priority zero means entry is not in-use.
|
||||
}
|
||||
|
||||
SuccessOrExit(error = PrepareJoinerFinalizeMessage(aProvisioningUrl, aVendorName, aVendorModel, aVendorSwVersion,
|
||||
aVendorData));
|
||||
|
||||
if (!mDiscerner.IsEmpty())
|
||||
{
|
||||
SteeringData::CalculateHashBitIndexes(mDiscerner, filterIndexes);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetIdFromIeeeEui64();
|
||||
SteeringData::CalculateHashBitIndexes(mId, filterIndexes);
|
||||
}
|
||||
|
||||
SuccessOrExit(error = Get<Mle::DiscoverScanner>().Discover(Mac::ChannelMask(0), Get<Mac::Mac>().GetPanId(),
|
||||
/* aJoiner */ true, /* aEnableFiltering */ true,
|
||||
&filterIndexes, HandleDiscoverResult, this));
|
||||
|
||||
mCompletionCallback.Set(aCallback, aContext);
|
||||
SetState(kStateDiscover);
|
||||
|
||||
error = Get<Seeker>().Start(EvaluateScanResult, this);
|
||||
|
||||
exit:
|
||||
if (error != kErrorNone)
|
||||
if ((error != kErrorNone) && shouldCleanup)
|
||||
{
|
||||
FreeJoinerFinalizeMessage();
|
||||
Get<Tmf::SecureAgent>().Close();
|
||||
Get<Seeker>().Stop();
|
||||
SetState(kStateIdle);
|
||||
}
|
||||
|
||||
LogWarnOnError(error, "start joiner");
|
||||
@@ -196,13 +178,13 @@ void Joiner::Finish(Error aError)
|
||||
case kStateEntrust:
|
||||
case kStateJoined:
|
||||
Get<Tmf::SecureAgent>().Disconnect();
|
||||
IgnoreError(Get<Ip6::Filter>().RemoveUnsecurePort(kJoinerUdpPort));
|
||||
mTimer.Stop();
|
||||
|
||||
OT_FALL_THROUGH;
|
||||
|
||||
case kStateDiscover:
|
||||
Get<Tmf::SecureAgent>().Close();
|
||||
Get<Seeker>().Stop();
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -215,114 +197,74 @@ exit:
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t Joiner::CalculatePriority(int8_t aRssi, bool aSteeringDataAllowsAny)
|
||||
Seeker::Verdict Joiner::EvaluateScanResult(void *aContext, const Seeker::ScanResult *aResult)
|
||||
{
|
||||
int16_t priority;
|
||||
return static_cast<Joiner *>(aContext)->EvaluateScanResult(aResult);
|
||||
}
|
||||
|
||||
if (aRssi == Radio::kInvalidRssi)
|
||||
Seeker::Verdict Joiner::EvaluateScanResult(const Seeker::ScanResult *aResult)
|
||||
{
|
||||
Seeker::Verdict verdict = Seeker::kIgnore;
|
||||
const SteeringData *steeringData;
|
||||
|
||||
if (aResult == nullptr)
|
||||
{
|
||||
aRssi = -127;
|
||||
HandleScanCompleted();
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
priority = Clamp<int8_t>(aRssi, -127, -1);
|
||||
steeringData = AsCoreTypePtr(&aResult->mSteeringData);
|
||||
|
||||
// Assign higher priority to networks with an exact match of Joiner
|
||||
// ID in the Steering Data (128 < priority < 256) compared to ones
|
||||
// that allow all Joiners (0 < priority < 128). Sub-prioritize
|
||||
// based on signal strength. Priority 0 is reserved for unused
|
||||
// entry.
|
||||
// We prefer networks with an exact match of Joiner ID or
|
||||
// Discerner in the Steering Data compared to ones that allow all
|
||||
// Joiners.
|
||||
|
||||
priority += aSteeringDataAllowsAny ? 128 : 256;
|
||||
|
||||
return static_cast<uint8_t>(priority);
|
||||
}
|
||||
|
||||
void Joiner::HandleDiscoverResult(Mle::DiscoverScanner::ScanResult *aResult, void *aContext)
|
||||
{
|
||||
static_cast<Joiner *>(aContext)->HandleDiscoverResult(aResult);
|
||||
}
|
||||
|
||||
void Joiner::HandleDiscoverResult(Mle::DiscoverScanner::ScanResult *aResult)
|
||||
{
|
||||
VerifyOrExit(mState == kStateDiscover);
|
||||
|
||||
if (aResult != nullptr)
|
||||
if (steeringData->PermitsAllJoiners())
|
||||
{
|
||||
SaveDiscoveredJoinerRouter(*aResult);
|
||||
verdict = Seeker::kAccept;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
if (!mDiscerner.IsEmpty())
|
||||
{
|
||||
VerifyOrExit(steeringData->Contains(mDiscerner));
|
||||
}
|
||||
else
|
||||
{
|
||||
Get<Mac::Mac>().SetExtAddress(mId);
|
||||
Get<Mle::Mle>().UpdateLinkLocalAddress();
|
||||
|
||||
mJoinerRouterIndex = 0;
|
||||
TryNextJoinerRouter(kErrorNone);
|
||||
VerifyOrExit(steeringData->Contains(mId));
|
||||
}
|
||||
|
||||
verdict = Seeker::kAcceptPreferred;
|
||||
|
||||
exit:
|
||||
return verdict;
|
||||
}
|
||||
|
||||
void Joiner::HandleScanCompleted(void)
|
||||
{
|
||||
VerifyOrExit(mState == kStateDiscover);
|
||||
|
||||
Get<Mac::Mac>().SetExtAddress(mId);
|
||||
Get<Mle::Mle>().UpdateLinkLocalAddress();
|
||||
|
||||
TryNextCandidate(kErrorNone);
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void Joiner::SaveDiscoveredJoinerRouter(const Mle::DiscoverScanner::ScanResult &aResult)
|
||||
void Joiner::TryNextCandidate(Error aPrevError)
|
||||
{
|
||||
uint8_t priority;
|
||||
bool doesAllowAny;
|
||||
JoinerRouter *end;
|
||||
JoinerRouter *entry;
|
||||
Error error;
|
||||
|
||||
VerifyOrExit(aResult.mJoinerUdpPort > 0);
|
||||
|
||||
doesAllowAny = AsCoreType(&aResult.mSteeringData).PermitsAllJoiners();
|
||||
|
||||
LogInfo("Joiner discover network: %s, pan:0x%04x, port:%d, chan:%d, rssi:%d, allow-any:%s",
|
||||
AsCoreType(&aResult.mExtAddress).ToString().AsCString(), aResult.mPanId, aResult.mJoinerUdpPort,
|
||||
aResult.mChannel, aResult.mRssi, ToYesNo(doesAllowAny));
|
||||
|
||||
priority = CalculatePriority(aResult.mRssi, doesAllowAny);
|
||||
|
||||
// We keep the list sorted based on priority. Find the place to
|
||||
// add the new result.
|
||||
|
||||
end = GetArrayEnd(mJoinerRouters);
|
||||
|
||||
for (entry = &mJoinerRouters[0]; entry < end; entry++)
|
||||
do
|
||||
{
|
||||
if (priority > entry->mPriority)
|
||||
error = ConnectToNextCandidate();
|
||||
|
||||
if (error == kErrorNone)
|
||||
{
|
||||
break;
|
||||
ExitNow();
|
||||
}
|
||||
}
|
||||
|
||||
VerifyOrExit(entry < end);
|
||||
|
||||
// Shift elements in array to make room for the new one.
|
||||
memmove(entry + 1, entry,
|
||||
static_cast<size_t>(reinterpret_cast<uint8_t *>(end - 1) - reinterpret_cast<uint8_t *>(entry)));
|
||||
|
||||
entry->mExtAddr = AsCoreType(&aResult.mExtAddress);
|
||||
entry->mPanId = aResult.mPanId;
|
||||
entry->mJoinerUdpPort = aResult.mJoinerUdpPort;
|
||||
entry->mChannel = aResult.mChannel;
|
||||
entry->mPriority = priority;
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void Joiner::TryNextJoinerRouter(Error aPrevError)
|
||||
{
|
||||
for (; mJoinerRouterIndex < GetArrayLength(mJoinerRouters); mJoinerRouterIndex++)
|
||||
{
|
||||
JoinerRouter &router = mJoinerRouters[mJoinerRouterIndex];
|
||||
Error error;
|
||||
|
||||
if (router.mPriority == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
error = Connect(router);
|
||||
VerifyOrExit(error != kErrorNone, mJoinerRouterIndex++);
|
||||
|
||||
// Save the error from `Connect` only if there is no previous
|
||||
// error from earlier attempts. This ensures that if there has
|
||||
@@ -331,16 +273,9 @@ void Joiner::TryNextJoinerRouter(Error aPrevError)
|
||||
// emitted from `Finish()` call corresponds to the error from
|
||||
// that attempt.
|
||||
|
||||
if (aPrevError == kErrorNone)
|
||||
{
|
||||
aPrevError = error;
|
||||
}
|
||||
}
|
||||
aPrevError = (aPrevError == kErrorNone) ? error : aPrevError;
|
||||
|
||||
if (aPrevError == kErrorNone)
|
||||
{
|
||||
aPrevError = kErrorNotFound;
|
||||
}
|
||||
} while (error != kErrorNotFound);
|
||||
|
||||
Finish(aPrevError);
|
||||
|
||||
@@ -348,26 +283,16 @@ exit:
|
||||
return;
|
||||
}
|
||||
|
||||
Error Joiner::Connect(JoinerRouter &aRouter)
|
||||
Error Joiner::ConnectToNextCandidate(void)
|
||||
{
|
||||
Error error = kErrorNotFound;
|
||||
Ip6::SockAddr sockAddr(aRouter.mJoinerUdpPort);
|
||||
|
||||
LogInfo("Joiner connecting to %s, pan:0x%04x, chan:%d", aRouter.mExtAddr.ToString().AsCString(), aRouter.mPanId,
|
||||
aRouter.mChannel);
|
||||
|
||||
Get<Mac::Mac>().SetPanId(aRouter.mPanId);
|
||||
SuccessOrExit(error = Get<Mac::Mac>().SetPanChannel(aRouter.mChannel));
|
||||
SuccessOrExit(error = Get<Ip6::Filter>().AddUnsecurePort(kJoinerUdpPort));
|
||||
|
||||
sockAddr.GetAddress().SetToLinkLocalAddress(aRouter.mExtAddr);
|
||||
Error error;
|
||||
Ip6::SockAddr sockAddr;
|
||||
|
||||
SuccessOrExit(error = Get<Seeker>().SetUpNextConnection(sockAddr));
|
||||
SuccessOrExit(error = Get<Tmf::SecureAgent>().Connect(sockAddr));
|
||||
|
||||
SetState(kStateConnect);
|
||||
|
||||
exit:
|
||||
LogWarnOnError(error, "start secure joiner connection");
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -388,7 +313,7 @@ void Joiner::HandleSecureCoapClientConnect(Dtls::Session::ConnectEvent aEvent)
|
||||
}
|
||||
else
|
||||
{
|
||||
TryNextJoinerRouter(kErrorSecurity);
|
||||
TryNextCandidate(kErrorSecurity);
|
||||
}
|
||||
|
||||
exit:
|
||||
@@ -491,7 +416,7 @@ void Joiner::HandleJoinerFinalizeResponse(Coap::Msg *aMsg, Error aResult)
|
||||
|
||||
exit:
|
||||
Get<Tmf::SecureAgent>().Disconnect();
|
||||
IgnoreError(Get<Ip6::Filter>().RemoveUnsecurePort(kJoinerUdpPort));
|
||||
Get<Seeker>().Stop();
|
||||
}
|
||||
|
||||
template <> void Joiner::HandleTmf<kUriJoinerEntrust>(Coap::Msg &aMsg)
|
||||
|
||||
+23
-37
@@ -52,7 +52,7 @@
|
||||
#include "meshcop/meshcop.hpp"
|
||||
#include "meshcop/meshcop_tlvs.hpp"
|
||||
#include "meshcop/secure_transport.hpp"
|
||||
#include "thread/discover_scanner.hpp"
|
||||
#include "meshcop/seeker.hpp"
|
||||
#include "thread/tmf.hpp"
|
||||
|
||||
namespace ot {
|
||||
@@ -135,7 +135,7 @@ public:
|
||||
const Mac::ExtAddress &GetId(void) const { return mId; }
|
||||
|
||||
/**
|
||||
* Gets the Jointer Discerner.
|
||||
* Gets the Joiner Discerner.
|
||||
*
|
||||
* @returns A pointer to the current Joiner Discerner or `nullptr` if none is set.
|
||||
*/
|
||||
@@ -178,40 +178,28 @@ public:
|
||||
static const char *StateToString(State aState);
|
||||
|
||||
private:
|
||||
static constexpr uint16_t kMaxJoinerRouterCandidates = OPENTHREAD_CONFIG_JOINER_MAX_CANDIDATES;
|
||||
static constexpr uint16_t kJoinerUdpPort = OPENTHREAD_CONFIG_JOINER_UDP_PORT;
|
||||
static constexpr uint32_t kConfigExtAddressDelay = 100; // in msec.
|
||||
static constexpr uint32_t kResponseTimeout = 4000; // in msec
|
||||
static constexpr uint32_t kConfigExtAddressDelay = 100; // in msec.
|
||||
static constexpr uint32_t kResponseTimeout = 4000; // in msec
|
||||
|
||||
struct JoinerRouter
|
||||
{
|
||||
Mac::ExtAddress mExtAddr;
|
||||
Mac::PanId mPanId;
|
||||
uint16_t mJoinerUdpPort;
|
||||
uint8_t mChannel;
|
||||
uint8_t mPriority;
|
||||
};
|
||||
|
||||
void SetState(State aState);
|
||||
void SetIdFromIeeeEui64(void);
|
||||
void SaveDiscoveredJoinerRouter(const Mle::DiscoverScanner::ScanResult &aResult);
|
||||
void TryNextJoinerRouter(Error aPrevError);
|
||||
Error Connect(JoinerRouter &aRouter);
|
||||
void Finish(Error aError);
|
||||
void HandleTimer(void);
|
||||
uint8_t CalculatePriority(int8_t aRssi, bool aSteeringDataAllowsAny);
|
||||
Error PrepareJoinerFinalizeMessage(const char *aProvisioningUrl,
|
||||
const char *aVendorName,
|
||||
const char *aVendorModel,
|
||||
const char *aVendorSwVersion,
|
||||
const char *aVendorData);
|
||||
void FreeJoinerFinalizeMessage(void);
|
||||
void SendJoinerFinalize(void);
|
||||
void SendJoinerEntrustResponse(const Coap::Msg &aMsg);
|
||||
static void HandleDiscoverResult(Mle::DiscoverScanner::ScanResult *aResult, void *aContext);
|
||||
void HandleDiscoverResult(Mle::DiscoverScanner::ScanResult *aResult);
|
||||
static void HandleSecureCoapClientConnect(Dtls::Session::ConnectEvent aEvent, void *aContext);
|
||||
void HandleSecureCoapClientConnect(Dtls::Session::ConnectEvent aEvent);
|
||||
void SetState(State aState);
|
||||
void SetIdFromIeeeEui64(void);
|
||||
void TryNextCandidate(Error aPrevError);
|
||||
Error ConnectToNextCandidate(void);
|
||||
void Finish(Error aError);
|
||||
void HandleTimer(void);
|
||||
Error PrepareJoinerFinalizeMessage(const char *aProvisioningUrl,
|
||||
const char *aVendorName,
|
||||
const char *aVendorModel,
|
||||
const char *aVendorSwVersion,
|
||||
const char *aVendorData);
|
||||
void FreeJoinerFinalizeMessage(void);
|
||||
void SendJoinerFinalize(void);
|
||||
void SendJoinerEntrustResponse(const Coap::Msg &aMsg);
|
||||
static Seeker::Verdict EvaluateScanResult(void *aContext, const Seeker::ScanResult *aResult);
|
||||
Seeker::Verdict EvaluateScanResult(const Seeker::ScanResult *aResult);
|
||||
void HandleScanCompleted(void);
|
||||
static void HandleSecureCoapClientConnect(Dtls::Session::ConnectEvent aEvent, void *aContext);
|
||||
void HandleSecureCoapClientConnect(Dtls::Session::ConnectEvent aEvent);
|
||||
|
||||
DeclareTmfResponseHandlerIn(Joiner, HandleJoinerFinalizeResponse);
|
||||
|
||||
@@ -223,8 +211,6 @@ private:
|
||||
JoinerDiscerner mDiscerner;
|
||||
State mState;
|
||||
Callback<CompletionCallback> mCompletionCallback;
|
||||
JoinerRouter mJoinerRouters[kMaxJoinerRouterCandidates];
|
||||
uint16_t mJoinerRouterIndex;
|
||||
Coap::Message *mFinalizeMessage;
|
||||
JoinerTimer mTimer;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright (c) 2026, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file implements the Seeker functionality.
|
||||
*/
|
||||
|
||||
#include "seeker.hpp"
|
||||
|
||||
#if OPENTHREAD_CONFIG_JOINER_ENABLE
|
||||
|
||||
#include "instance/instance.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace MeshCoP {
|
||||
|
||||
RegisterLogModule("Seeker");
|
||||
|
||||
Seeker::Seeker(Instance &aInstance)
|
||||
: InstanceLocator(aInstance)
|
||||
, mState(kStateStopped)
|
||||
, mCandidateIndex(0)
|
||||
{
|
||||
}
|
||||
|
||||
Error Seeker::Start(ScanEvaluator aScanEvaluator, void *aContext)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
Mac::ExtAddress randomAddress;
|
||||
|
||||
VerifyOrExit(aScanEvaluator != nullptr, error = kErrorInvalidArgs);
|
||||
|
||||
VerifyOrExit(GetState() == kStateStopped, error = kErrorBusy);
|
||||
VerifyOrExit(Get<ThreadNetif>().IsUp() && Get<Mle::Mle>().IsDisabled(), error = kErrorInvalidState);
|
||||
|
||||
randomAddress.GenerateRandom();
|
||||
Get<Mac::Mac>().SetExtAddress(randomAddress);
|
||||
Get<Mle::Mle>().UpdateLinkLocalAddress();
|
||||
|
||||
mScanEvaluator.Set(aScanEvaluator, aContext);
|
||||
|
||||
ClearAllBytes(mCandidates);
|
||||
mCandidateIndex = 0;
|
||||
|
||||
SuccessOrExit(error =
|
||||
Get<Mle::DiscoverScanner>().Discover(Mac::ChannelMask(0), Get<Mac::Mac>().GetPanId(),
|
||||
/* aJoiner */ true, /* aEnableFiltering */ false,
|
||||
/* aFilterIndexes */ nullptr, HandleDiscoverResult, this));
|
||||
SetState(kStateDiscovering);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
void Seeker::Stop(void)
|
||||
{
|
||||
switch (GetState())
|
||||
{
|
||||
case kStateStopped:
|
||||
case kStateDiscovering:
|
||||
case kStateDiscoverDone:
|
||||
break;
|
||||
case kStateConnecting:
|
||||
IgnoreError(Get<Ip6::Filter>().RemoveUnsecurePort(kUdpPort));
|
||||
break;
|
||||
}
|
||||
|
||||
SetState(kStateStopped);
|
||||
}
|
||||
|
||||
void Seeker::HandleDiscoverResult(ScanResult *aResult, void *aContext)
|
||||
{
|
||||
static_cast<Seeker *>(aContext)->HandleDiscoverResult(aResult);
|
||||
}
|
||||
|
||||
void Seeker::HandleDiscoverResult(ScanResult *aResult)
|
||||
{
|
||||
bool preferred = false;
|
||||
|
||||
VerifyOrExit(GetState() == kStateDiscovering);
|
||||
|
||||
if (aResult == nullptr)
|
||||
{
|
||||
SetState(kStateDiscoverDone);
|
||||
IgnoreReturnValue(mScanEvaluator.Invoke(aResult));
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
VerifyOrExit(aResult->mJoinerUdpPort > 0);
|
||||
VerifyOrExit(AsCoreType(&aResult->mSteeringData).IsValid());
|
||||
|
||||
switch (mScanEvaluator.Invoke(aResult))
|
||||
{
|
||||
case kAccept:
|
||||
break;
|
||||
case kAcceptPreferred:
|
||||
preferred = true;
|
||||
break;
|
||||
|
||||
case kIgnore:
|
||||
default:
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
SaveCandidate(*aResult, preferred);
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
void Seeker::SaveCandidate(const ScanResult &aResult, bool aPreferred)
|
||||
{
|
||||
uint8_t priority;
|
||||
Candidate *end;
|
||||
Candidate *entry;
|
||||
|
||||
LogInfo("Discovered: %s, pan:0x%04x, port:%u, chan:%u, rssi:%d, preferred:%s",
|
||||
AsCoreType(&aResult.mExtAddress).ToString().AsCString(), aResult.mPanId, aResult.mJoinerUdpPort,
|
||||
aResult.mChannel, aResult.mRssi, ToYesNo(aPreferred));
|
||||
|
||||
priority = CalculatePriority(aResult.mRssi, aPreferred);
|
||||
|
||||
// We keep the list sorted based on priority. Find the place to
|
||||
// add the new result.
|
||||
|
||||
end = GetArrayEnd(mCandidates);
|
||||
|
||||
for (entry = &mCandidates[0]; entry < end; entry++)
|
||||
{
|
||||
if (priority > entry->mPriority)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
VerifyOrExit(entry < end);
|
||||
|
||||
// Shift elements in array to make room for the new one.
|
||||
memmove(entry + 1, entry,
|
||||
static_cast<size_t>(reinterpret_cast<uint8_t *>(end - 1) - reinterpret_cast<uint8_t *>(entry)));
|
||||
|
||||
entry->mExtAddr = AsCoreType(&aResult.mExtAddress);
|
||||
entry->mPanId = aResult.mPanId;
|
||||
entry->mJoinerUdpPort = aResult.mJoinerUdpPort;
|
||||
entry->mChannel = aResult.mChannel;
|
||||
entry->mPriority = priority;
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t Seeker::CalculatePriority(int8_t aRssi, bool aPreferred)
|
||||
{
|
||||
int16_t priority;
|
||||
|
||||
if (aRssi == Radio::kInvalidRssi)
|
||||
{
|
||||
aRssi = -127;
|
||||
}
|
||||
|
||||
priority = Clamp<int8_t>(aRssi, -127, -1);
|
||||
|
||||
// We assign a higher priority value to networks marked as
|
||||
// preferred (128 < priority < 256) compared to normal
|
||||
// (0 < priority < 128). Sub-prioritize based on signal
|
||||
// strength. Priority 0 is reserved for unused entry.
|
||||
|
||||
priority += aPreferred ? 256 : 128;
|
||||
|
||||
return static_cast<uint8_t>(priority);
|
||||
}
|
||||
|
||||
Error Seeker::SetUpNextConnection(Ip6::SockAddr &aSockAddr)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
const Candidate *candidate;
|
||||
|
||||
switch (GetState())
|
||||
{
|
||||
case kStateDiscoverDone:
|
||||
case kStateConnecting:
|
||||
break;
|
||||
|
||||
case kStateStopped:
|
||||
case kStateDiscovering:
|
||||
ExitNow(error = kErrorInvalidState);
|
||||
}
|
||||
|
||||
candidate = &mCandidates[mCandidateIndex];
|
||||
|
||||
if (!candidate->IsValid())
|
||||
{
|
||||
Stop();
|
||||
ExitNow(error = kErrorNotFound);
|
||||
}
|
||||
|
||||
mCandidateIndex++;
|
||||
|
||||
LogInfo("Setting up conn to %s, pan:0x%04x, chan:%u", candidate->mExtAddr.ToString().AsCString(), candidate->mPanId,
|
||||
candidate->mChannel);
|
||||
|
||||
Get<Mac::Mac>().SetPanId(candidate->mPanId);
|
||||
SuccessOrExit(error = Get<Mac::Mac>().SetPanChannel(candidate->mChannel));
|
||||
|
||||
if (!Get<Ip6::Filter>().IsUnsecurePort(kUdpPort))
|
||||
{
|
||||
SuccessOrExit(error = Get<Ip6::Filter>().AddUnsecurePort(kUdpPort));
|
||||
}
|
||||
|
||||
SetState(kStateConnecting);
|
||||
|
||||
aSockAddr.Clear();
|
||||
aSockAddr.SetPort(candidate->mJoinerUdpPort);
|
||||
aSockAddr.GetAddress().SetToLinkLocalAddress(candidate->mExtAddr);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
} // namespace MeshCoP
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_JOINER_ENABLE
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2026, The OpenThread Authors.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. Neither the name of the copyright holder nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file includes definitions for the Seeker module.
|
||||
*
|
||||
* The Seeker is responsible for discovering nearby Joiner Router candidates, prioritizing them, and
|
||||
* iterating through the list to select the next best candidate for connection. It acts as a
|
||||
* sub-module of the `Joiner` class.
|
||||
*/
|
||||
|
||||
#ifndef OT_CORE_MESHCOP_SEEKER_HPP_
|
||||
#define OT_CORE_MESHCOP_SEEKER_HPP_
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#if OPENTHREAD_CONFIG_JOINER_ENABLE
|
||||
|
||||
#include "common/callback.hpp"
|
||||
#include "common/error.hpp"
|
||||
#include "common/locator.hpp"
|
||||
#include "common/non_copyable.hpp"
|
||||
#include "mac/mac_types.hpp"
|
||||
#include "net/socket.hpp"
|
||||
#include "thread/discover_scanner.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace MeshCoP {
|
||||
|
||||
/**
|
||||
* Represents a MeshCoP Seeker.
|
||||
*/
|
||||
class Seeker : public InstanceLocator, private NonCopyable
|
||||
{
|
||||
public:
|
||||
static constexpr uint16_t kUdpPort = OPENTHREAD_CONFIG_JOINER_UDP_PORT; ///< The default Joiner UDP port.
|
||||
|
||||
typedef Mle::DiscoverScanner::ScanResult ScanResult; ///< Discover Scan result.
|
||||
|
||||
/**
|
||||
* Represents a verdict returned from `ScanEvaluator` when evaluating a Discover Scan result.
|
||||
*/
|
||||
enum Verdict : uint8_t
|
||||
{
|
||||
kAccept, ///< The scan result is acceptable.
|
||||
kAcceptPreferred, ///< The scan result is acceptable and preferred.
|
||||
kIgnore, ///< The scan result should be ignored.
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the callback function type used to evaluate a scan result or report the end of a scan.
|
||||
*
|
||||
* @param[in] aContext A pointer to the callback context.
|
||||
* @param[in] aResult A pointer to the scan result to evaluate, or `nullptr` to indicate scan completion.
|
||||
*
|
||||
* @returns The verdict for the scan result (`kAccept`, `kAcceptPreferred`, or `kIgnore`).
|
||||
* If @p aResult is `nullptr` (scan complete), the return value is ignored.
|
||||
*/
|
||||
typedef Verdict (*ScanEvaluator)(void *aContext, const ScanResult *aResult);
|
||||
|
||||
/**
|
||||
* Initializes the `Seeker`
|
||||
*
|
||||
* @param[in] aInstance The OpenThread instance.
|
||||
*/
|
||||
explicit Seeker(Instance &aInstance);
|
||||
|
||||
/**
|
||||
* Starts the Seeker operation.
|
||||
*
|
||||
* The Seeker generates and sets a random MAC address for anonymity, then initiates an MLE Discover Scan to find
|
||||
* Joiner Router candidates.
|
||||
*
|
||||
* Found candidates are reported to the @p aScanEvaluator callback. Based on the returned `Verdict`, the Seeker
|
||||
* maintains a prioritized list of candidates for future connection attempts.
|
||||
*
|
||||
* @param[in] aScanEvaluator The callback function to evaluate scan results.
|
||||
* @param[in] aContext An arbitrary context pointer to use with @p aScanEvaluator.
|
||||
*
|
||||
* @retval kErrorNone Successfully started the Seeker.
|
||||
* @retval kErrorBusy The Seeker is already active (scanning or connecting).
|
||||
* @retval kErrorInvalidState The IPv6 interface is not enabled, or MLE is enabled.
|
||||
*/
|
||||
Error Start(ScanEvaluator aScanEvaluator, void *aContext);
|
||||
|
||||
/**
|
||||
* Stops the Seeker operation.
|
||||
*
|
||||
* This method stops any ongoing discovery or connection process, unregisters the unsecure Joiner UDP port, and
|
||||
* clears internal state. If the Seeker is already stopped, this method has no effect.
|
||||
*
|
||||
* If the join process succeeds after a call to `SetupNextConnection()`, the caller MUST call this method to stop
|
||||
* the Seeker and, importantly, unregister the Joiner UDP port.
|
||||
*
|
||||
* Note: If `SetupNextConnection()` returns `kErrorNotFound` (indicating the candidate list is exhausted), the
|
||||
* Seeker stops automatically.
|
||||
*/
|
||||
void Stop(void);
|
||||
|
||||
/**
|
||||
* Indicates whether or not the Seeker is running.
|
||||
*
|
||||
* @retval TRUE The seeker is active and running.
|
||||
* @retval FALSE The seeker is stopped.
|
||||
*/
|
||||
bool IsRunning(void) const { return GetState() != kStateStopped; }
|
||||
|
||||
/**
|
||||
* Selects the next best candidate and prepares the connection.
|
||||
*
|
||||
* This method must be called after the discovery scan has completed (indicated by the `ScanEvaluator` callback
|
||||
* receiving `nullptr`). Calling it before scan completion will result in `kErrorInvalidState`.
|
||||
*
|
||||
* This method iterates through the discovered Joiner Router candidates in order of priority. For the selected
|
||||
* candidate, it configures the radio channel and PAN ID, and populates @p aSockAddr with the candidate's address.
|
||||
* It also registers the Joiner UDP port `kUdpPort` as an unsecure port to allow UDP
|
||||
* connection to the candidate.
|
||||
*
|
||||
* If the list is exhausted, this method returns `kErrorNotFound` and automatically calls `Stop()`, which removes
|
||||
* the unsecure port and clears internal state.
|
||||
*
|
||||
* @param[out] aSockAddr A reference to a socket address to output the candidate's address.
|
||||
*
|
||||
* @retval kErrorNone Successfully set up the connection to the next candidate.
|
||||
* @retval kErrorNotFound No more candidates are available (list exhausted).
|
||||
* @retval kErrorInvalidState The Seeker is not in a valid state (e.g. scan not yet completed).
|
||||
*/
|
||||
Error SetUpNextConnection(Ip6::SockAddr &aSockAddr);
|
||||
|
||||
private:
|
||||
static constexpr uint16_t kMaxCandidates = OPENTHREAD_CONFIG_JOINER_MAX_CANDIDATES;
|
||||
|
||||
enum State : uint8_t
|
||||
{
|
||||
kStateStopped,
|
||||
kStateDiscovering,
|
||||
kStateDiscoverDone,
|
||||
kStateConnecting,
|
||||
};
|
||||
|
||||
struct Candidate
|
||||
{
|
||||
bool IsValid(void) const { return mPriority != 0; }
|
||||
|
||||
Mac::ExtAddress mExtAddr;
|
||||
Mac::PanId mPanId;
|
||||
uint16_t mJoinerUdpPort;
|
||||
uint8_t mChannel;
|
||||
uint8_t mPriority;
|
||||
};
|
||||
|
||||
State GetState(void) const { return mState; }
|
||||
void SetState(State aState) { mState = aState; }
|
||||
static void HandleDiscoverResult(ScanResult *aResult, void *aContext);
|
||||
void HandleDiscoverResult(ScanResult *aResult);
|
||||
void SaveCandidate(const ScanResult &aResult, bool aPreferred);
|
||||
static uint8_t CalculatePriority(int8_t aRssi, bool aPreferred);
|
||||
|
||||
State mState;
|
||||
Callback<ScanEvaluator> mScanEvaluator;
|
||||
Candidate mCandidates[kMaxCandidates];
|
||||
uint16_t mCandidateIndex;
|
||||
};
|
||||
|
||||
} // namespace MeshCoP
|
||||
} // namespace ot
|
||||
|
||||
#endif // OPENTHREAD_CONFIG_JOINER_ENABLE
|
||||
|
||||
#endif // OT_CORE_MESHCOP_SEEKER_HPP_
|
||||
Reference in New Issue
Block a user