Skip to content

Commit

Permalink
[thread host] implement join for rcp host
Browse files Browse the repository at this point in the history
  • Loading branch information
Irving-cl committed Dec 12, 2024
1 parent 19aa69f commit 59d2a90
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 3 deletions.
84 changes: 81 additions & 3 deletions src/ncp/rcp_host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,7 @@ void RcpHost::Deinit(void)
mThreadEnabledStateChangedCallbacks.clear();
mResetHandlers.clear();

mJoinReceiver = nullptr;
mSetThreadEnabledReceiver = nullptr;
mScheduleMigrationReceiver = nullptr;
mDetachGracefullyCallbacks.clear();
Expand All @@ -344,6 +345,12 @@ void RcpHost::HandleStateChanged(otChangedFlags aFlags)
}

mThreadHelper->StateChangedCallback(aFlags);

if ((aFlags & OT_CHANGED_THREAD_ROLE) && IsAttached() && mJoinReceiver != nullptr)
{
otbrLogInfo("Join succeeded");
SafeInvokeAndClear(mJoinReceiver, OT_ERROR_NONE, "Join succeeded");
}
}

void RcpHost::Update(MainloopContext &aMainloop)
Expand Down Expand Up @@ -438,12 +445,82 @@ const char *RcpHost::GetThreadVersion(void)
return version;
}

static bool noNeedRejoin(const otOperationalDatasetTlvs &aLhs, const otOperationalDatasetTlvs &aRhs)
{
bool result = false;

otOperationalDataset lhsDataset;
otOperationalDataset rhsDataset;

SuccessOrExit(otDatasetParseTlvs(&aLhs, &lhsDataset));
SuccessOrExit(otDatasetParseTlvs(&aRhs, &rhsDataset));

result =
(lhsDataset.mChannel == rhsDataset.mChannel) &&
(memcmp(lhsDataset.mNetworkKey.m8, rhsDataset.mNetworkKey.m8, sizeof(lhsDataset.mNetworkKey)) == 0) &&
(memcmp(lhsDataset.mExtendedPanId.m8, rhsDataset.mExtendedPanId.m8, sizeof(lhsDataset.mExtendedPanId)) == 0);

exit:
return result;
}

void RcpHost::Join(const otOperationalDatasetTlvs &aActiveOpDatasetTlvs, const AsyncResultReceiver &aReceiver)
{
OT_UNUSED_VARIABLE(aActiveOpDatasetTlvs);
otError error = OT_ERROR_NONE;
std::string errorMsg;
bool receiveResultHere = true;
otOperationalDatasetTlvs curDatasetTlvs;

VerifyOrExit(mInstance != nullptr, error = OT_ERROR_INVALID_STATE, errorMsg = "OT is not initialized");
VerifyOrExit(mThreadEnabledState != ThreadEnabledState::kStateDisabling, error = OT_ERROR_BUSY,
errorMsg = "Thread is disabling");
VerifyOrExit(mThreadEnabledState == ThreadEnabledState::kStateEnabled, error = OT_ERROR_INVALID_STATE,
errorMsg = "Thread is not enabled");

otbrLogInfo("Start joining...");

error = otDatasetGetActiveTlvs(mInstance, &curDatasetTlvs);
if (error == OT_ERROR_NONE && noNeedRejoin(aActiveOpDatasetTlvs, curDatasetTlvs) && IsAttached())
{
// Do not leave and re-join if this device has already joined the same network. This can help elimilate
// unnecessary connectivity and topology disruption and save the time for re-joining. It's more useful for use
// cases where Thread networks are dynamically brought up and torn down (e.g. Thread on mobile phones).
SuccessOrExit(error = otDatasetSetActiveTlvs(mInstance, &aActiveOpDatasetTlvs),
errorMsg = "Failed to set Active Operational Dataset");
errorMsg = "Already Joined the target network";
ExitNow();
}

if (GetDeviceRole() != OT_DEVICE_ROLE_DISABLED)
{
ThreadDetachGracefully([aActiveOpDatasetTlvs, aReceiver, this] {
ConditionalErasePersistentInfo(true);
Join(aActiveOpDatasetTlvs, aReceiver);
});
receiveResultHere = false;
ExitNow();
}

SuccessOrExit(error = otDatasetSetActiveTlvs(mInstance, &aActiveOpDatasetTlvs),
errorMsg = "Failed to set Active Operational Dataset");

// TODO: Implement Join under RCP mode.
mTaskRunner.Post([aReceiver](void) { aReceiver(OT_ERROR_NOT_IMPLEMENTED, "Not implemented!"); });
// TODO(b/273160198): check how we can implement join as a child
SuccessOrExit(error = otIp6SetEnabled(mInstance, true), errorMsg = "Failed to bring up Thread interface");
SuccessOrExit(error = otThreadSetEnabled(mInstance, true), errorMsg = "Failed to bring up Thread stack");

// Abort an ongoing join()
if (mJoinReceiver != nullptr)
{
SafeInvoke(mJoinReceiver, OT_ERROR_ABORT, "Join() is aborted");
}
mJoinReceiver = aReceiver;
receiveResultHere = false;

exit:
if (receiveResultHere)
{
mTaskRunner.Post([aReceiver, error, errorMsg](void) { aReceiver(error, errorMsg); });
}
}

void RcpHost::Leave(bool aEraseDataset, const AsyncResultReceiver &aReceiver)
Expand Down Expand Up @@ -636,6 +713,7 @@ void RcpHost::ThreadDetachGracefullyCallback(void *aContext)

void RcpHost::ThreadDetachGracefullyCallback(void)
{
SafeInvokeAndClear(mJoinReceiver, OT_ERROR_ABORT, "Aborted by leave/disable operation");
SafeInvokeAndClear(mScheduleMigrationReceiver, OT_ERROR_ABORT, "Aborted by leave/disable operation");

for (auto &callback : mDetachGracefullyCallbacks)
Expand Down
1 change: 1 addition & 0 deletions src/ncp/rcp_host.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ class RcpHost : public MainloopProcessor, public ThreadHost, public OtNetworkPro
std::vector<ThreadEnabledStateCallback> mThreadEnabledStateChangedCallbacks;
bool mEnableAutoAttach = false;
ThreadEnabledState mThreadEnabledState;
AsyncResultReceiver mJoinReceiver;
AsyncResultReceiver mSetThreadEnabledReceiver;
AsyncResultReceiver mScheduleMigrationReceiver;
std::vector<DetachGracefullyCallback> mDetachGracefullyCallbacks;
Expand Down
119 changes: 119 additions & 0 deletions tests/gtest/test_rcp_host_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,122 @@ TEST(RcpHostApi, StateChangesCorrectlyAfterScheduleMigration)

host.Deinit();
}

TEST(RcpHostApi, StateChangesCorrectlyAfterJoin)
{
otError error = OT_ERROR_NONE;
otError error_ = OT_ERROR_NONE;
std::string errorMsg = "";
std::string errorMsg_ = "";
bool resultReceived = false;
bool resultReceived_ = false;
otbr::MainloopContext mainloop;
otbr::Ncp::ThreadHost::AsyncResultReceiver receiver = [&resultReceived, &error,
&errorMsg](otError aError, const std::string &aErrorMsg) {
resultReceived = true;
error = aError;
errorMsg = aErrorMsg;
};
otbr::Ncp::ThreadHost::AsyncResultReceiver receiver_ = [&resultReceived_, &error_,
&errorMsg_](otError aError, const std::string &aErrorMsg) {
resultReceived_ = true;
error_ = aError;
errorMsg_ = aErrorMsg;
};
otbr::Ncp::RcpHost host("wpan0", std::vector<const char *>(), /* aBackboneInterfaceName */ "", /* aDryRun */ false,
/* aEnableAutoAttach */ false);

otOperationalDataset dataset;
(void)dataset;
otOperationalDatasetTlvs datasetTlvs;

// 1. Call Join when host hasn't been initialized.
otbr::MainloopManager::GetInstance().RemoveMainloopProcessor(
&host); // Temporarily remove RcpHost because it's not initialized yet.
host.Join(datasetTlvs, receiver);
MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
EXPECT_EQ(error, OT_ERROR_INVALID_STATE);
EXPECT_STREQ(errorMsg.c_str(), "OT is not initialized");
otbr::MainloopManager::GetInstance().AddMainloopProcessor(&host);

host.Init();
OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset));
otDatasetConvertToTlvs(&dataset, &datasetTlvs);

// 2. Call Join when Thread is not enabled.
error = OT_ERROR_NONE;
resultReceived = false;
host.Join(datasetTlvs, receiver);
MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
EXPECT_EQ(error, OT_ERROR_INVALID_STATE);
EXPECT_STREQ(errorMsg.c_str(), "Thread is not enabled");

// 3. Call two consecutive Join. The first one should be aborted. The second one should succeed.
error = OT_ERROR_NONE;
resultReceived = false;
host.SetThreadEnabled(true, receiver);
MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
error = OT_ERROR_NONE;
resultReceived = false;
host.Join(datasetTlvs, receiver_);
host.Join(datasetTlvs, receiver);

MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0,
[&resultReceived, &resultReceived_]() { return resultReceived && resultReceived_; });
EXPECT_EQ(error_, OT_ERROR_ABORT);
EXPECT_STREQ(errorMsg_.c_str(), "Aborted by leave/disable operation"); // The second Join will trigger Leave first.
EXPECT_EQ(error, OT_ERROR_NONE);
EXPECT_STREQ(errorMsg.c_str(), "Join succeeded");
EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_LEADER);

// 4. Call Join with the same dataset.
error = OT_ERROR_NONE;
resultReceived = false;
host.Join(datasetTlvs, receiver);
MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
EXPECT_EQ(error, OT_ERROR_NONE);
EXPECT_STREQ(errorMsg.c_str(), "Already Joined the target network");

// 5. Call Disable right after Join (Already Attached).
error = OT_ERROR_NONE;
resultReceived = false;
error_ = OT_ERROR_NONE;
resultReceived_ = false;

OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset));
otDatasetConvertToTlvs(&dataset, &datasetTlvs); // Use a different dataset.

host.Join(datasetTlvs, receiver_);
host.SetThreadEnabled(false, receiver);

MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0,
[&resultReceived, &resultReceived_]() { return resultReceived && resultReceived_; });
EXPECT_EQ(error_, OT_ERROR_BUSY);
EXPECT_STREQ(errorMsg_.c_str(), "Thread is disabling");
EXPECT_EQ(error, OT_ERROR_NONE);
EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_DISABLED);

// 6. Call Disable right after Join (not attached).
resultReceived = false;
host.Leave(true, receiver); // Leave the network first.
MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
resultReceived = false; // Enale Thread.
host.SetThreadEnabled(true, receiver);
MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });

error = OT_ERROR_NONE;
resultReceived = false;
error_ = OT_ERROR_NONE;
resultReceived_ = false;
host.Join(datasetTlvs, receiver_);
host.SetThreadEnabled(false, receiver);

MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0,
[&resultReceived, &resultReceived_]() { return resultReceived && resultReceived_; });
EXPECT_EQ(error_, OT_ERROR_ABORT);
EXPECT_STREQ(errorMsg_.c_str(), "Aborted by leave/disable operation");
EXPECT_EQ(error, OT_ERROR_NONE);
EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_DISABLED);

host.Deinit();
}

0 comments on commit 59d2a90

Please sign in to comment.