Skip to content

Commit

Permalink
[thread-host] add ThreadEnabled state callback (#2612)
Browse files Browse the repository at this point in the history
This commit adds `AddThreadEnabledStateChangedCallback` to
`ThreadHost` and implements it for RcpHost.

Since the `ThreadEnabledState` is subscribed by some platform
services, (for example, on Android) and this state is also required to
implement `Join`, `Leave`, `ScheduleMigration` and `SetThreadEnabled`,
this commit adds the `ThreadEnabledState` to `RcpHost` and provide an
API to allow other components to subscribe to this state.

This commit makes the behavior of `ScheduleMigration` and
`SetThreadEnabled` same as the Android binder API. And the unit test
is updated accordingly.
  • Loading branch information
Irving-cl authored Nov 21, 2024
1 parent bd7bc77 commit fe769eb
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 26 deletions.
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)
{
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; });
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

0 comments on commit fe769eb

Please sign in to comment.