/* * Copyright (c) 2024, 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 #include #include "common/mainloop.hpp" #include "common/mainloop_manager.hpp" #include "ncp/rcp_host.hpp" #include "fake_platform.hpp" static void MainloopProcessUntil(otbr::MainloopContext &aMainloop, uint32_t aTimeoutSec, std::function aCondition) { timeval startTime; timeval now; gettimeofday(&startTime, nullptr); while (!aCondition()) { gettimeofday(&now, nullptr); // Simply compare the second. We don't need high precision here. if (now.tv_sec - startTime.tv_sec > aTimeoutSec) { break; } otbr::MainloopManager::GetInstance().Update(aMainloop); otbr::MainloopManager::GetInstance().Process(aMainloop); } } TEST(RcpHostApi, DeviceRoleChangesCorrectlyAfterSetThreadEnabled) { otError error = OT_ERROR_FAILED; bool resultReceived = false; 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::RcpHost host("wpan0", std::vector(), /* aBackboneInterfaceName */ "", /* aDryRun */ false, /* aEnableAutoAttach */ false); host.Init(); // 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); // 2. Set active dataset and enable it { otOperationalDataset dataset; otOperationalDatasetTlvs datasetTlvs; OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset)); otDatasetConvertToTlvs(&dataset, &datasetTlvs); OT_UNUSED_VARIABLE(otDatasetSetActiveTlvs(ot::FakePlatform::CurrentInstance(), &datasetTlvs)); } 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); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1, [&host]() { return host.GetDeviceRole() != OT_DEVICE_ROLE_DETACHED; }); EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_LEADER); // 3. Disable it error = OT_ERROR_FAILED; resultReceived = false; host.SetThreadEnabled(false, receiver); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1, [&resultReceived]() { return resultReceived; }); EXPECT_EQ(error, OT_ERROR_NONE); EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_DISABLED); // 4. Duplicate call, should get OT_ERROR_BUSY error = OT_ERROR_FAILED; resultReceived = false; otError error2 = OT_ERROR_FAILED; bool resultReceived2 = false; host.SetThreadEnabled(false, receiver); host.SetThreadEnabled(false, [&resultReceived2, &error2](otError aError, const std::string &aErrorMsg) { OT_UNUSED_VARIABLE(aErrorMsg); error2 = aError; resultReceived2 = true; }); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1, [&resultReceived, &resultReceived2]() { return resultReceived && resultReceived2; }); EXPECT_EQ(error, OT_ERROR_NONE); EXPECT_EQ(error2, OT_ERROR_BUSY); host.Deinit(); } TEST(RcpHostApi, SetCountryCodeWorkCorrectly) { otError error = OT_ERROR_FAILED; bool resultReceived = false; 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::RcpHost host("wpan0", std::vector(), /* aBackboneInterfaceName */ "", /* aDryRun */ false, /* aEnableAutoAttach */ false); // 1. Call SetCountryCode when host hasn't been initialized. otbr::MainloopManager::GetInstance().RemoveMainloopProcessor( &host); // Temporarily remove RcpHost because it's not initialized yet. host.SetCountryCode("AF", receiver); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; }); EXPECT_EQ(error, OT_ERROR_INVALID_STATE); otbr::MainloopManager::GetInstance().AddMainloopProcessor(&host); host.Init(); // 2. Call SetCountryCode with invalid arguments resultReceived = false; error = OT_ERROR_NONE; host.SetCountryCode("AFA", receiver); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; }); EXPECT_EQ(error, OT_ERROR_INVALID_ARGS); resultReceived = false; error = OT_ERROR_NONE; host.SetCountryCode("A", receiver); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; }); EXPECT_EQ(error, OT_ERROR_INVALID_ARGS); resultReceived = false; error = OT_ERROR_NONE; host.SetCountryCode("12", receiver); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; }); EXPECT_EQ(error, OT_ERROR_INVALID_ARGS); // 3. Call SetCountryCode with valid argument resultReceived = false; error = OT_ERROR_NONE; host.SetCountryCode("AF", receiver); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; }); EXPECT_EQ(error, OT_ERROR_NOT_IMPLEMENTED); // The current platform weak implmentation returns 'NOT_IMPLEMENTED'. host.Deinit(); } TEST(RcpHostApi, StateChangesCorrectlyAfterScheduleMigration) { otError error = OT_ERROR_NONE; bool resultReceived = false; 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::RcpHost host("wpan0", std::vector(), /* aBackboneInterfaceName */ "", /* aDryRun */ false, /* aEnableAutoAttach */ false); otOperationalDataset dataset; otOperationalDatasetTlvs datasetTlvs; // 1. Call ScheduleMigration when host hasn't been initialized. otbr::MainloopManager::GetInstance().RemoveMainloopProcessor( &host); // Temporarily remove RcpHost because it's not initialized yet. host.ScheduleMigration(datasetTlvs, receiver); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; }); EXPECT_EQ(error, OT_ERROR_INVALID_STATE); otbr::MainloopManager::GetInstance().AddMainloopProcessor(&host); host.Init(); // 2. Call ScheduleMigration when the device is not attached. error = OT_ERROR_NONE; resultReceived = false; host.ScheduleMigration(datasetTlvs, receiver); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; }); EXPECT_EQ(error, OT_ERROR_FAILED); // 3. Schedule migration to another network. OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset)); otDatasetConvertToTlvs(&dataset, &datasetTlvs); OT_UNUSED_VARIABLE(otDatasetSetActiveTlvs(ot::FakePlatform::CurrentInstance(), &datasetTlvs)); error = OT_ERROR_NONE; resultReceived = false; host.SetThreadEnabled(true, receiver); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1, [&host]() { return host.GetDeviceRole() != OT_DEVICE_ROLE_DETACHED; }); EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_LEADER); host.ScheduleMigration(datasetTlvs, receiver); MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; }); EXPECT_EQ(error, OT_ERROR_NONE); host.Deinit(); }