Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[thread-host] add ThreadEnabled state callback #2612

Merged
merged 1 commit into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/ncp/ncp_host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ void NcpHost::AddThreadStateChangedCallback(ThreadStateChangedCallback aCallback
OT_UNUSED_VARIABLE(aCallback);
}

void NcpHost::AddThreadEnabledStateChangedCallback(ThreadEnabledStateCallback aCallback)
{
// TODO: Implement AddThreadEnabledStateChangedCallback under NCP mode.
OT_UNUSED_VARIABLE(aCallback);
}

void NcpHost::Process(const MainloopContext &aMainloop)
{
mSpinelDriver.Process(&aMainloop);
Expand Down
1 change: 1 addition & 0 deletions src/ncp/ncp_host.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ class NcpHost : public MainloopProcessor, public ThreadHost, public NcpNetworkPr
const AsyncResultReceiver &aReceiver) override;
#endif
void AddThreadStateChangedCallback(ThreadStateChangedCallback aCallback) override;
void AddThreadEnabledStateChangedCallback(ThreadEnabledStateCallback aCallback) override;
CoprocessorType GetCoprocessorType(void) override
{
return OT_COPROCESSOR_NCP;
Expand Down
48 changes: 41 additions & 7 deletions src/ncp/rcp_host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ RcpHost::RcpHost(const char *aInterfaceName,
bool aEnableAutoAttach)
: mInstance(nullptr)
, mEnableAutoAttach(aEnableAutoAttach)
, mThreadEnabledState(ThreadEnabledState::kStateDisabled)
{
VerifyOrDie(aRadioUrls.size() <= OT_PLATFORM_CONFIG_MAX_RADIO_URLS, "Too many Radio URLs!");

Expand Down Expand Up @@ -327,6 +328,7 @@ void RcpHost::Deinit(void)

OtNetworkProperties::SetInstance(nullptr);
mThreadStateChangedCallbacks.clear();
mThreadEnabledStateChangedCallbacks.clear();
mResetHandlers.clear();

mSetThreadEnabledReceiver = nullptr;
Expand Down Expand Up @@ -390,6 +392,11 @@ void RcpHost::AddThreadStateChangedCallback(ThreadStateChangedCallback aCallback
mThreadStateChangedCallbacks.emplace_back(std::move(aCallback));
}

void RcpHost::AddThreadEnabledStateChangedCallback(ThreadEnabledStateCallback aCallback)
{
mThreadEnabledStateChangedCallbacks.push_back(aCallback);
}

void RcpHost::Reset(void)
{
gPlatResetReason = OT_PLAT_RESET_REASON_SOFTWARE;
Expand Down Expand Up @@ -452,8 +459,13 @@ void RcpHost::ScheduleMigration(const otOperationalDatasetTlvs &aPendingOpDatase
otOperationalDataset emptyDataset;

VerifyOrExit(mInstance != nullptr, error = OT_ERROR_INVALID_STATE, errorMsg = "OT is not initialized");
VerifyOrExit(IsAttached(), error = OT_ERROR_FAILED,
errorMsg = "Cannot schedule migration when this device is detached");

VerifyOrExit(mThreadEnabledState != ThreadEnabledState::kStateDisabling, error = OT_ERROR_BUSY,
errorMsg = "Thread is disabling");
VerifyOrExit(mThreadEnabledState == ThreadEnabledState::kStateEnabled, error = OT_ERROR_INVALID_STATE,
errorMsg = "Thread is disabled");

VerifyOrExit(IsAttached(), error = OT_ERROR_INVALID_STATE, errorMsg = "Device is detached");

// TODO: check supported channel mask

Expand Down Expand Up @@ -488,25 +500,35 @@ void RcpHost::SendMgmtPendingSetCallback(otError aError)

void RcpHost::SetThreadEnabled(bool aEnabled, const AsyncResultReceiver aReceiver)
{
otError error = OT_ERROR_NONE;
bool receiveResultHere = true;
otError error = OT_ERROR_NONE;
std::string errorMsg = "";
bool receiveResultHere = true;

VerifyOrExit(mInstance != nullptr, error = OT_ERROR_INVALID_STATE);
VerifyOrExit(mSetThreadEnabledReceiver == nullptr, error = OT_ERROR_BUSY);
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");

if (aEnabled)
{
otOperationalDatasetTlvs datasetTlvs;

if (mThreadEnabledState == ThreadEnabledState::kStateEnabled)
{
ExitNow();
}

if (otDatasetGetActiveTlvs(mInstance, &datasetTlvs) != OT_ERROR_NOT_FOUND && datasetTlvs.mLength > 0 &&
otThreadGetDeviceRole(mInstance) == OT_DEVICE_ROLE_DISABLED)
{
SuccessOrExit(error = otIp6SetEnabled(mInstance, true));
SuccessOrExit(error = otThreadSetEnabled(mInstance, true));
}
UpdateThreadEnabledState(ThreadEnabledState::kStateEnabled);
}
else
{
UpdateThreadEnabledState(ThreadEnabledState::kStateDisabling);

SuccessOrExit(error = otThreadDetachGracefully(mInstance, DisableThreadAfterDetach, this));
mSetThreadEnabledReceiver = aReceiver;
receiveResultHere = false;
Expand All @@ -515,7 +537,7 @@ void RcpHost::SetThreadEnabled(bool aEnabled, const AsyncResultReceiver aReceive
exit:
if (receiveResultHere)
{
mTaskRunner.Post([aReceiver, error](void) { aReceiver(error, ""); });
mTaskRunner.Post([aReceiver, error, errorMsg](void) { aReceiver(error, errorMsg); });
}
}

Expand Down Expand Up @@ -586,6 +608,8 @@ void RcpHost::DisableThreadAfterDetach(void)
SuccessOrExit(error = otThreadSetEnabled(mInstance, false), errorMsg = "Failed to disable Thread stack");
SuccessOrExit(error = otIp6SetEnabled(mInstance, false), errorMsg = "Failed to disable Thread interface");

UpdateThreadEnabledState(ThreadEnabledState::kStateDisabled);

exit:
SafeInvokeAndClear(mSetThreadEnabledReceiver, error, errorMsg);
}
Expand Down Expand Up @@ -617,6 +641,16 @@ bool RcpHost::IsAttached(void)
return role == OT_DEVICE_ROLE_CHILD || role == OT_DEVICE_ROLE_ROUTER || role == OT_DEVICE_ROLE_LEADER;
}

void RcpHost::UpdateThreadEnabledState(ThreadEnabledState aState)
{
mThreadEnabledState = aState;

for (auto &callback : mThreadEnabledStateChangedCallbacks)
{
callback(mThreadEnabledState);
}
}

/*
* Provide, if required an "otPlatLog()" function
*/
Expand Down
13 changes: 9 additions & 4 deletions src/ncp/rcp_host.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ class RcpHost : public MainloopProcessor, public ThreadHost, public OtNetworkPro
const AsyncResultReceiver &aReceiver) override;
#endif
void AddThreadStateChangedCallback(ThreadStateChangedCallback aCallback) override;
void AddThreadEnabledStateChangedCallback(ThreadEnabledStateCallback aCallback) override;

CoprocessorType GetCoprocessorType(void) override
{
Expand Down Expand Up @@ -260,6 +261,8 @@ class RcpHost : public MainloopProcessor, public ThreadHost, public OtNetworkPro

bool IsAttached(void);

void UpdateThreadEnabledState(ThreadEnabledState aState);

otError SetOtbrAndOtLogLevel(otbrLogLevel aLevel);

otInstance *mInstance;
Expand All @@ -268,11 +271,13 @@ class RcpHost : public MainloopProcessor, public ThreadHost, public OtNetworkPro
std::unique_ptr<otbr::agent::ThreadHelper> mThreadHelper;
std::vector<std::function<void(void)>> mResetHandlers;
TaskRunner mTaskRunner;
std::vector<ThreadStateChangedCallback> mThreadStateChangedCallbacks;
bool mEnableAutoAttach = false;

AsyncResultReceiver mSetThreadEnabledReceiver;
AsyncResultReceiver mScheduleMigrationReceiver;
std::vector<ThreadStateChangedCallback> mThreadStateChangedCallbacks;
std::vector<ThreadEnabledStateCallback> mThreadEnabledStateChangedCallbacks;
bool mEnableAutoAttach = false;
ThreadEnabledState mThreadEnabledState;
AsyncResultReceiver mSetThreadEnabledReceiver;
AsyncResultReceiver mScheduleMigrationReceiver;

#if OTBR_ENABLE_FEATURE_FLAGS
// The applied FeatureFlagList in ApplyFeatureFlagList call, used for debugging purpose.
Expand Down
16 changes: 16 additions & 0 deletions src/ncp/thread_host.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@ class NetworkProperties
virtual ~NetworkProperties(void) = default;
};

enum ThreadEnabledState
{
kStateDisabled = 0,
kStateEnabled = 1,
kStateDisabling = 2,
kStateInvalid = 255,
};

/**
* This class is an interface which provides a set of async APIs to control the
* Thread network.
Expand All @@ -112,6 +120,7 @@ class ThreadHost : virtual public NetworkProperties
std::function<void(uint32_t /*aSupportedChannelMask*/, uint32_t /*aPreferredChannelMask*/)>;
using DeviceRoleHandler = std::function<void(otError, otDeviceRole)>;
using ThreadStateChangedCallback = std::function<void(otChangedFlags aFlags)>;
using ThreadEnabledStateCallback = std::function<void(ThreadEnabledState aState)>;

struct ChannelMaxPower
{
Expand Down Expand Up @@ -233,6 +242,13 @@ class ThreadHost : virtual public NetworkProperties
*/
virtual void AddThreadStateChangedCallback(ThreadStateChangedCallback aCallback) = 0;

/**
* This method adds a event listener for Thread Enabled state changes.
*
* @param[in] aCallback The callback to receive Thread Enabled state changed events.
*/
virtual void AddThreadEnabledStateChangedCallback(ThreadEnabledStateCallback aCallback) = 0;

/**
* Returns the co-processor type.
*/
Expand Down
45 changes: 30 additions & 15 deletions tests/gtest/test_rcp_host_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,54 +64,65 @@ static void MainloopProcessUntil(otbr::MainloopContext &aMainloop,

TEST(RcpHostApi, DeviceRoleChangesCorrectlyAfterSetThreadEnabled)
Irving-cl marked this conversation as resolved.
Show resolved Hide resolved
{
otError error = OT_ERROR_FAILED;
bool resultReceived = false;
otError error = OT_ERROR_FAILED;
bool resultReceived = false;
otbr::Ncp::ThreadEnabledState threadEnabledState = otbr::Ncp::ThreadEnabledState::kStateInvalid;
otbr::MainloopContext mainloop;
otbr::Ncp::ThreadHost::AsyncResultReceiver receiver = [&resultReceived, &error](otError aError,
const std::string &aErrorMsg) {
OT_UNUSED_VARIABLE(aErrorMsg);
resultReceived = true;
error = aError;
};
otbr::Ncp::ThreadHost::ThreadEnabledStateCallback enabledStateCallback =
[&threadEnabledState](otbr::Ncp::ThreadEnabledState aState) { threadEnabledState = aState; };
otbr::Ncp::RcpHost host("wpan0", std::vector<const char *>(), /* aBackboneInterfaceName */ "", /* aDryRun */ false,
/* aEnableAutoAttach */ false);

host.Init();
host.AddThreadEnabledStateChangedCallback(enabledStateCallback);

// 1. Active dataset hasn't been set, should succeed with device role still being disabled.
host.SetThreadEnabled(true, receiver);
MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1, [&resultReceived]() { return resultReceived; });
EXPECT_EQ(error, OT_ERROR_NONE);
EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_DISABLED);
EXPECT_EQ(threadEnabledState, otbr::Ncp::ThreadEnabledState::kStateEnabled);

// 2. Set active dataset and enable it
// 2. Set active dataset and start it
{
otOperationalDataset dataset;
otOperationalDatasetTlvs datasetTlvs;
OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset));
otDatasetConvertToTlvs(&dataset, &datasetTlvs);
OT_UNUSED_VARIABLE(otDatasetSetActiveTlvs(ot::FakePlatform::CurrentInstance(), &datasetTlvs));
}
OT_UNUSED_VARIABLE(otIp6SetEnabled(ot::FakePlatform::CurrentInstance(), true));
OT_UNUSED_VARIABLE(otThreadSetEnabled(ot::FakePlatform::CurrentInstance(), true));

MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1,
[&host]() { return host.GetDeviceRole() != OT_DEVICE_ROLE_DETACHED; });
EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_LEADER);

// 3. Enable again, the enabled state should not change.
error = OT_ERROR_FAILED;
resultReceived = false;
host.SetThreadEnabled(true, receiver);
MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1, [&resultReceived]() { return resultReceived; });
EXPECT_EQ(error, OT_ERROR_NONE);
EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_DETACHED);
EXPECT_EQ(threadEnabledState, otbr::Ncp::ThreadEnabledState::kStateEnabled);

MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1,
[&host]() { return host.GetDeviceRole() != OT_DEVICE_ROLE_DETACHED; });
EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_LEADER);

// 3. Disable it
// 4. Disable it
error = OT_ERROR_FAILED;
resultReceived = false;
host.SetThreadEnabled(false, receiver);
EXPECT_EQ(threadEnabledState, otbr::Ncp::ThreadEnabledState::kStateDisabling);
MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1, [&resultReceived]() { return resultReceived; });
Irving-cl marked this conversation as resolved.
Show resolved Hide resolved
EXPECT_EQ(error, OT_ERROR_NONE);
EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_DISABLED);
EXPECT_EQ(threadEnabledState, otbr::Ncp::ThreadEnabledState::kStateDisabled);

// 4. Duplicate call, should get OT_ERROR_BUSY
// 5. Duplicate call, should get OT_ERROR_BUSY
error = OT_ERROR_FAILED;
resultReceived = false;
otError error2 = OT_ERROR_FAILED;
Expand All @@ -126,6 +137,7 @@ TEST(RcpHostApi, DeviceRoleChangesCorrectlyAfterSetThreadEnabled)
[&resultReceived, &resultReceived2]() { return resultReceived && resultReceived2; });
EXPECT_EQ(error, OT_ERROR_NONE);
EXPECT_EQ(error2, OT_ERROR_BUSY);
EXPECT_EQ(threadEnabledState, otbr::Ncp::ThreadEnabledState::kStateDisabled);

host.Deinit();
}
Expand Down Expand Up @@ -185,13 +197,14 @@ TEST(RcpHostApi, SetCountryCodeWorkCorrectly)
TEST(RcpHostApi, StateChangesCorrectlyAfterScheduleMigration)
{
otError error = OT_ERROR_NONE;
std::string errorMsg = "";
bool resultReceived = false;
otbr::MainloopContext mainloop;
otbr::Ncp::ThreadHost::AsyncResultReceiver receiver = [&resultReceived, &error](otError aError,
const std::string &aErrorMsg) {
OT_UNUSED_VARIABLE(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);
Expand All @@ -205,16 +218,18 @@ TEST(RcpHostApi, StateChangesCorrectlyAfterScheduleMigration)
host.ScheduleMigration(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();

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

// 3. Schedule migration to another network.
OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset));
Expand Down
Loading