• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  *    Copyright (c) 2024, The OpenThread Authors.
3  *    All rights reserved.
4  *
5  *    Redistribution and use in source and binary forms, with or without
6  *    modification, are permitted provided that the following conditions are met:
7  *    1. Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *    2. Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *    3. Neither the name of the copyright holder nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *    POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <gmock/gmock.h>
30 #include <gtest/gtest.h>
31 
32 #include <sys/time.h>
33 
34 #include <openthread/dataset.h>
35 #include <openthread/dataset_ftd.h>
36 
37 #include "common/mainloop.hpp"
38 #include "common/mainloop_manager.hpp"
39 #include "host/rcp_host.hpp"
40 
41 #include "fake_platform.hpp"
42 
MainloopProcessUntil(otbr::MainloopContext & aMainloop,uint32_t aTimeoutSec,std::function<bool (void)> aCondition)43 static void MainloopProcessUntil(otbr::MainloopContext    &aMainloop,
44                                  uint32_t                  aTimeoutSec,
45                                  std::function<bool(void)> aCondition)
46 {
47     timeval startTime;
48     timeval now;
49     gettimeofday(&startTime, nullptr);
50 
51     while (!aCondition())
52     {
53         gettimeofday(&now, nullptr);
54         // Simply compare the second. We don't need high precision here.
55         if (now.tv_sec - startTime.tv_sec > aTimeoutSec)
56         {
57             break;
58         }
59 
60         otbr::MainloopManager::GetInstance().Update(aMainloop);
61         otbr::MainloopManager::GetInstance().Process(aMainloop);
62     }
63 }
64 
TEST(RcpHostApi,DeviceRoleChangesCorrectlyAfterSetThreadEnabled)65 TEST(RcpHostApi, DeviceRoleChangesCorrectlyAfterSetThreadEnabled)
66 {
67     otError                                     error              = OT_ERROR_FAILED;
68     bool                                        resultReceived     = false;
69     otbr::Host::ThreadEnabledState              threadEnabledState = otbr::Host::ThreadEnabledState::kStateInvalid;
70     otbr::MainloopContext                       mainloop;
71     otbr::Host::ThreadHost::AsyncResultReceiver receiver = [&resultReceived, &error](otError            aError,
72                                                                                      const std::string &aErrorMsg) {
73         OT_UNUSED_VARIABLE(aErrorMsg);
74         resultReceived = true;
75         error          = aError;
76     };
77     otbr::Host::ThreadHost::ThreadEnabledStateCallback enabledStateCallback =
78         [&threadEnabledState](otbr::Host::ThreadEnabledState aState) { threadEnabledState = aState; };
79     otbr::Host::RcpHost host("wpan0", std::vector<const char *>(), /* aBackboneInterfaceName */ "", /* aDryRun */ false,
80                              /* aEnableAutoAttach */ false);
81 
82     host.Init();
83     host.AddThreadEnabledStateChangedCallback(enabledStateCallback);
84 
85     // 1. Active dataset hasn't been set, should succeed with device role still being disabled.
86     host.SetThreadEnabled(true, receiver);
87     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1, [&resultReceived]() { return resultReceived; });
88     EXPECT_EQ(error, OT_ERROR_NONE);
89     EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_DISABLED);
90     EXPECT_EQ(threadEnabledState, otbr::Host::ThreadEnabledState::kStateEnabled);
91 
92     // 2. Set active dataset and start it
93     {
94         otOperationalDataset     dataset;
95         otOperationalDatasetTlvs datasetTlvs;
96         OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset));
97         otDatasetConvertToTlvs(&dataset, &datasetTlvs);
98         OT_UNUSED_VARIABLE(otDatasetSetActiveTlvs(ot::FakePlatform::CurrentInstance(), &datasetTlvs));
99     }
100     OT_UNUSED_VARIABLE(otIp6SetEnabled(ot::FakePlatform::CurrentInstance(), true));
101     OT_UNUSED_VARIABLE(otThreadSetEnabled(ot::FakePlatform::CurrentInstance(), true));
102 
103     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1,
104                          [&host]() { return host.GetDeviceRole() != OT_DEVICE_ROLE_DETACHED; });
105     EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_LEADER);
106 
107     // 3. Enable again, the enabled state should not change.
108     error          = OT_ERROR_FAILED;
109     resultReceived = false;
110     host.SetThreadEnabled(true, receiver);
111     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1, [&resultReceived]() { return resultReceived; });
112     EXPECT_EQ(error, OT_ERROR_NONE);
113     EXPECT_EQ(threadEnabledState, otbr::Host::ThreadEnabledState::kStateEnabled);
114 
115     // 4. Disable it
116     error          = OT_ERROR_FAILED;
117     resultReceived = false;
118     host.SetThreadEnabled(false, receiver);
119     EXPECT_EQ(threadEnabledState, otbr::Host::ThreadEnabledState::kStateDisabling);
120     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1, [&resultReceived]() { return resultReceived; });
121     EXPECT_EQ(error, OT_ERROR_NONE);
122     EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_DISABLED);
123     EXPECT_EQ(threadEnabledState, otbr::Host::ThreadEnabledState::kStateDisabled);
124 
125     // 5. Duplicate call, should get OT_ERROR_BUSY
126     error                   = OT_ERROR_FAILED;
127     resultReceived          = false;
128     otError error2          = OT_ERROR_FAILED;
129     bool    resultReceived2 = false;
130     host.SetThreadEnabled(false, receiver);
131     host.SetThreadEnabled(false, [&resultReceived2, &error2](otError aError, const std::string &aErrorMsg) {
132         OT_UNUSED_VARIABLE(aErrorMsg);
133         error2          = aError;
134         resultReceived2 = true;
135     });
136     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1,
137                          [&resultReceived, &resultReceived2]() { return resultReceived && resultReceived2; });
138     EXPECT_EQ(error, OT_ERROR_NONE);
139     EXPECT_EQ(error2, OT_ERROR_BUSY);
140     EXPECT_EQ(threadEnabledState, otbr::Host::ThreadEnabledState::kStateDisabled);
141 
142     host.Deinit();
143 }
144 
TEST(RcpHostApi,SetCountryCodeWorkCorrectly)145 TEST(RcpHostApi, SetCountryCodeWorkCorrectly)
146 {
147     otError                                     error          = OT_ERROR_FAILED;
148     bool                                        resultReceived = false;
149     otbr::MainloopContext                       mainloop;
150     otbr::Host::ThreadHost::AsyncResultReceiver receiver = [&resultReceived, &error](otError            aError,
151                                                                                      const std::string &aErrorMsg) {
152         OT_UNUSED_VARIABLE(aErrorMsg);
153         resultReceived = true;
154         error          = aError;
155     };
156     otbr::Host::RcpHost host("wpan0", std::vector<const char *>(), /* aBackboneInterfaceName */ "", /* aDryRun */ false,
157                              /* aEnableAutoAttach */ false);
158 
159     // 1. Call SetCountryCode when host hasn't been initialized.
160     otbr::MainloopManager::GetInstance().RemoveMainloopProcessor(
161         &host); // Temporarily remove RcpHost because it's not initialized yet.
162     host.SetCountryCode("AF", receiver);
163     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
164     EXPECT_EQ(error, OT_ERROR_INVALID_STATE);
165     otbr::MainloopManager::GetInstance().AddMainloopProcessor(&host);
166 
167     host.Init();
168     // 2. Call SetCountryCode with invalid arguments
169     resultReceived = false;
170     error          = OT_ERROR_NONE;
171     host.SetCountryCode("AFA", receiver);
172     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
173     EXPECT_EQ(error, OT_ERROR_INVALID_ARGS);
174 
175     resultReceived = false;
176     error          = OT_ERROR_NONE;
177     host.SetCountryCode("A", receiver);
178     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
179     EXPECT_EQ(error, OT_ERROR_INVALID_ARGS);
180 
181     resultReceived = false;
182     error          = OT_ERROR_NONE;
183     host.SetCountryCode("12", receiver);
184     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
185     EXPECT_EQ(error, OT_ERROR_INVALID_ARGS);
186 
187     // 3. Call SetCountryCode with valid argument
188     resultReceived = false;
189     error          = OT_ERROR_NONE;
190     host.SetCountryCode("AF", receiver);
191     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
192     EXPECT_EQ(error, OT_ERROR_NOT_IMPLEMENTED); // The current platform weak implmentation returns 'NOT_IMPLEMENTED'.
193 
194     host.Deinit();
195 }
196 
TEST(RcpHostApi,StateChangesCorrectlyAfterLeave)197 TEST(RcpHostApi, StateChangesCorrectlyAfterLeave)
198 {
199     otError                                     error          = OT_ERROR_NONE;
200     std::string                                 errorMsg       = "";
201     bool                                        resultReceived = false;
202     otbr::MainloopContext                       mainloop;
203     otbr::Host::ThreadHost::AsyncResultReceiver receiver = [&resultReceived, &error,
204                                                             &errorMsg](otError aError, const std::string &aErrorMsg) {
205         resultReceived = true;
206         error          = aError;
207         errorMsg       = aErrorMsg;
208     };
209 
210     otbr::Host::RcpHost host("wpan0", std::vector<const char *>(), /* aBackboneInterfaceName */ "", /* aDryRun */ false,
211                              /* aEnableAutoAttach */ false);
212 
213     // 1. Call Leave when host hasn't been initialized.
214     otbr::MainloopManager::GetInstance().RemoveMainloopProcessor(
215         &host); // Temporarily remove RcpHost because it's not initialized yet.
216     host.Leave(/* aEraseDataset */ true, receiver);
217     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
218     EXPECT_EQ(error, OT_ERROR_INVALID_STATE);
219     EXPECT_STREQ(errorMsg.c_str(), "OT is not initialized");
220     otbr::MainloopManager::GetInstance().AddMainloopProcessor(&host);
221 
222     host.Init();
223 
224     // 2. Call Leave when disabling Thread.
225     error          = OT_ERROR_NONE;
226     resultReceived = false;
227     host.SetThreadEnabled(false, nullptr);
228     host.Leave(/* aEraseDataset */ true, receiver);
229     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
230     EXPECT_EQ(error, OT_ERROR_BUSY);
231     EXPECT_STREQ(errorMsg.c_str(), "Thread is disabling");
232 
233     // 3. Call Leave when Thread is disabled.
234     error          = OT_ERROR_NONE;
235     resultReceived = false;
236     otOperationalDataset     dataset;
237     otOperationalDatasetTlvs datasetTlvs;
238     OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset));
239     otDatasetConvertToTlvs(&dataset, &datasetTlvs);
240     OT_UNUSED_VARIABLE(otDatasetSetActiveTlvs(ot::FakePlatform::CurrentInstance(), &datasetTlvs));
241     host.Leave(/* aEraseDataset */ true, receiver);
242     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
243     EXPECT_EQ(error, OT_ERROR_NONE);
244 
245     error = otDatasetGetActive(ot::FakePlatform::CurrentInstance(), &dataset);
246     EXPECT_EQ(error, OT_ERROR_NOT_FOUND);
247 
248     // 4. Call Leave when Thread is enabled.
249     error          = OT_ERROR_NONE;
250     resultReceived = false;
251     OT_UNUSED_VARIABLE(otDatasetSetActiveTlvs(ot::FakePlatform::CurrentInstance(), &datasetTlvs));
252     host.SetThreadEnabled(true, nullptr);
253     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1,
254                          [&host]() { return host.GetDeviceRole() != OT_DEVICE_ROLE_DETACHED; });
255     EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_LEADER);
256     host.Leave(/* aEraseDataset */ false, receiver);
257     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
258     EXPECT_EQ(error, OT_ERROR_NONE);
259 
260     error = otDatasetGetActive(ot::FakePlatform::CurrentInstance(), &dataset); // Dataset should still be there.
261     EXPECT_EQ(error, OT_ERROR_NONE);
262 
263     host.Deinit();
264 }
265 
TEST(RcpHostApi,StateChangesCorrectlyAfterScheduleMigration)266 TEST(RcpHostApi, StateChangesCorrectlyAfterScheduleMigration)
267 {
268     otError                                     error          = OT_ERROR_NONE;
269     std::string                                 errorMsg       = "";
270     bool                                        resultReceived = false;
271     otbr::MainloopContext                       mainloop;
272     otbr::Host::ThreadHost::AsyncResultReceiver receiver = [&resultReceived, &error,
273                                                             &errorMsg](otError aError, const std::string &aErrorMsg) {
274         resultReceived = true;
275         error          = aError;
276         errorMsg       = aErrorMsg;
277     };
278     otbr::Host::RcpHost host("wpan0", std::vector<const char *>(), /* aBackboneInterfaceName */ "", /* aDryRun */ false,
279                              /* aEnableAutoAttach */ false);
280 
281     otOperationalDataset     dataset;
282     otOperationalDatasetTlvs datasetTlvs;
283 
284     // 1. Call ScheduleMigration when host hasn't been initialized.
285     otbr::MainloopManager::GetInstance().RemoveMainloopProcessor(
286         &host); // Temporarily remove RcpHost because it's not initialized yet.
287     host.ScheduleMigration(datasetTlvs, receiver);
288     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
289     EXPECT_EQ(error, OT_ERROR_INVALID_STATE);
290     EXPECT_STREQ(errorMsg.c_str(), "OT is not initialized");
291     otbr::MainloopManager::GetInstance().AddMainloopProcessor(&host);
292 
293     host.Init();
294 
295     // 2. Call ScheduleMigration when the Thread is not enabled.
296     error          = OT_ERROR_NONE;
297     resultReceived = false;
298     host.ScheduleMigration(datasetTlvs, receiver);
299     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
300     EXPECT_EQ(error, OT_ERROR_INVALID_STATE);
301     EXPECT_STREQ(errorMsg.c_str(), "Thread is disabled");
302 
303     // 3. Schedule migration to another network.
304     OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset));
305     otDatasetConvertToTlvs(&dataset, &datasetTlvs);
306     OT_UNUSED_VARIABLE(otDatasetSetActiveTlvs(ot::FakePlatform::CurrentInstance(), &datasetTlvs));
307     error          = OT_ERROR_NONE;
308     resultReceived = false;
309     host.SetThreadEnabled(true, receiver);
310     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 1,
311                          [&host]() { return host.GetDeviceRole() != OT_DEVICE_ROLE_DETACHED; });
312     EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_LEADER);
313 
314     host.ScheduleMigration(datasetTlvs, receiver);
315     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
316     EXPECT_EQ(error, OT_ERROR_NONE);
317 
318     host.Deinit();
319 }
320 
TEST(RcpHostApi,StateChangesCorrectlyAfterJoin)321 TEST(RcpHostApi, StateChangesCorrectlyAfterJoin)
322 {
323     otError                                     error           = OT_ERROR_NONE;
324     otError                                     error_          = OT_ERROR_NONE;
325     std::string                                 errorMsg        = "";
326     std::string                                 errorMsg_       = "";
327     bool                                        resultReceived  = false;
328     bool                                        resultReceived_ = false;
329     otbr::MainloopContext                       mainloop;
330     otbr::Host::ThreadHost::AsyncResultReceiver receiver = [&resultReceived, &error,
331                                                             &errorMsg](otError aError, const std::string &aErrorMsg) {
332         resultReceived = true;
333         error          = aError;
334         errorMsg       = aErrorMsg;
335     };
336     otbr::Host::ThreadHost::AsyncResultReceiver receiver_ = [&resultReceived_, &error_,
337                                                              &errorMsg_](otError aError, const std::string &aErrorMsg) {
338         resultReceived_ = true;
339         error_          = aError;
340         errorMsg_       = aErrorMsg;
341     };
342     otbr::Host::RcpHost host("wpan0", std::vector<const char *>(), /* aBackboneInterfaceName */ "", /* aDryRun */ false,
343                              /* aEnableAutoAttach */ false);
344 
345     otOperationalDataset dataset;
346     (void)dataset;
347     otOperationalDatasetTlvs datasetTlvs;
348 
349     // 1. Call Join when host hasn't been initialized.
350     otbr::MainloopManager::GetInstance().RemoveMainloopProcessor(
351         &host); // Temporarily remove RcpHost because it's not initialized yet.
352     host.Join(datasetTlvs, receiver);
353     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
354     EXPECT_EQ(error, OT_ERROR_INVALID_STATE);
355     EXPECT_STREQ(errorMsg.c_str(), "OT is not initialized");
356     otbr::MainloopManager::GetInstance().AddMainloopProcessor(&host);
357 
358     host.Init();
359     OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset));
360     otDatasetConvertToTlvs(&dataset, &datasetTlvs);
361 
362     // 2. Call Join when Thread is not enabled.
363     error          = OT_ERROR_NONE;
364     resultReceived = false;
365     host.Join(datasetTlvs, receiver);
366     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
367     EXPECT_EQ(error, OT_ERROR_INVALID_STATE);
368     EXPECT_STREQ(errorMsg.c_str(), "Thread is not enabled");
369 
370     // 3. Call two consecutive Join. The first one should be aborted. The second one should succeed.
371     error          = OT_ERROR_NONE;
372     resultReceived = false;
373     host.SetThreadEnabled(true, receiver);
374     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
375     error          = OT_ERROR_NONE;
376     resultReceived = false;
377     host.Join(datasetTlvs, receiver_);
378     host.Join(datasetTlvs, receiver);
379 
380     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0,
381                          [&resultReceived, &resultReceived_]() { return resultReceived && resultReceived_; });
382     EXPECT_EQ(error_, OT_ERROR_ABORT);
383     EXPECT_STREQ(errorMsg_.c_str(), "Aborted by leave/disable operation"); // The second Join will trigger Leave first.
384     EXPECT_EQ(error, OT_ERROR_NONE);
385     EXPECT_STREQ(errorMsg.c_str(), "Join succeeded");
386     EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_LEADER);
387 
388     // 4. Call Join with the same dataset.
389     error          = OT_ERROR_NONE;
390     resultReceived = false;
391     host.Join(datasetTlvs, receiver);
392     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
393     EXPECT_EQ(error, OT_ERROR_NONE);
394     EXPECT_STREQ(errorMsg.c_str(), "Already Joined the target network");
395 
396     // 5. Call Disable right after Join (Already Attached).
397     error           = OT_ERROR_NONE;
398     resultReceived  = false;
399     error_          = OT_ERROR_NONE;
400     resultReceived_ = false;
401 
402     OT_UNUSED_VARIABLE(otDatasetCreateNewNetwork(ot::FakePlatform::CurrentInstance(), &dataset));
403     otDatasetConvertToTlvs(&dataset, &datasetTlvs); // Use a different dataset.
404 
405     host.Join(datasetTlvs, receiver_);
406     host.SetThreadEnabled(false, receiver);
407 
408     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0,
409                          [&resultReceived, &resultReceived_]() { return resultReceived && resultReceived_; });
410     EXPECT_EQ(error_, OT_ERROR_BUSY);
411     EXPECT_STREQ(errorMsg_.c_str(), "Thread is disabling");
412     EXPECT_EQ(error, OT_ERROR_NONE);
413     EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_DISABLED);
414 
415     // 6. Call Disable right after Join (not attached).
416     resultReceived = false;
417     host.Leave(true, receiver); // Leave the network first.
418     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
419     resultReceived = false; // Enale Thread.
420     host.SetThreadEnabled(true, receiver);
421     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0, [&resultReceived]() { return resultReceived; });
422 
423     error           = OT_ERROR_NONE;
424     resultReceived  = false;
425     error_          = OT_ERROR_NONE;
426     resultReceived_ = false;
427     host.Join(datasetTlvs, receiver_);
428     host.SetThreadEnabled(false, receiver);
429 
430     MainloopProcessUntil(mainloop, /* aTimeoutSec */ 0,
431                          [&resultReceived, &resultReceived_]() { return resultReceived && resultReceived_; });
432     EXPECT_EQ(error_, OT_ERROR_ABORT);
433     EXPECT_STREQ(errorMsg_.c_str(), "Aborted by leave/disable operation");
434     EXPECT_EQ(error, OT_ERROR_NONE);
435     EXPECT_EQ(host.GetDeviceRole(), OT_DEVICE_ROLE_DISABLED);
436 
437     host.Deinit();
438 }
439