Files
openthread/tests/unit/test_mle.cpp
T
Abtin Keshavarzian f60b00695e [mle] fix implementation of IsMinimalEndDevice() (#11926)
A Minimal End Device (MED) is defined by the spec as an MTD whose
receiver is enabled all the time.

This commit fixes the implementation of `IsMinimalEndDevice()` to
match this definition. The previous implementation of this method
would incorrectly accept any MTD config (including SED or MED).

Within the core modules, `IsMinimalEndDevice()` is only used in
`MeshForwarder::UpdateIp6Route()`. This commit updates the logic
in this method to differentiate between an FTD (calling
`UpdateIp6RouteFtd`()) and an MTD (forwarding to parent) by
checking `IsFullThreadDevice()` instead of the previous check
against `IsMinimalEndDevice()` (which was effectively checking
MTD).

This commit also adds a new unit test, `TestDeviceMode()`, to
validate all configurations of `DeviceMode` (SED, MED, FTD) and
verify the behavior of `IsMinimalEndDevice()`.
2025-09-15 11:31:43 -07:00

341 lines
12 KiB
C++

/*
* Copyright (c) 2023, 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 <openthread/config.h>
#include "test_platform.h"
#include "test_util.hpp"
#include "common/num_utils.hpp"
#include "thread/mle_types.hpp"
namespace ot {
void TestDeviceMode(void)
{
Mle::DeviceMode mode;
Mle::DeviceMode::ModeConfig config;
Mle::DeviceMode::ModeConfig readConfig;
//- - - - - - - - - - - - - - - - - - - - - - - -
// SED (stable subset netdata)
config.mRxOnWhenIdle = false;
config.mDeviceType = false;
config.mNetworkData = false;
mode.Set(config);
mode.Get(readConfig);
VerifyOrQuit(!readConfig.mRxOnWhenIdle);
VerifyOrQuit(!readConfig.mDeviceType);
VerifyOrQuit(!readConfig.mNetworkData);
VerifyOrQuit(mode.IsValid());
VerifyOrQuit(!mode.IsRxOnWhenIdle());
VerifyOrQuit(!mode.IsFullThreadDevice());
VerifyOrQuit(mode.GetNetworkDataType() == NetworkData::kStableSubset);
VerifyOrQuit(!mode.IsMinimalEndDevice());
//- - - - - - - - - - - - - - - - - - - - - - - -
// SED (full set netdata)
config.mRxOnWhenIdle = false;
config.mDeviceType = false;
config.mNetworkData = true;
mode.Set(config);
mode.Get(readConfig);
VerifyOrQuit(!readConfig.mRxOnWhenIdle);
VerifyOrQuit(!readConfig.mDeviceType);
VerifyOrQuit(readConfig.mNetworkData);
VerifyOrQuit(mode.IsValid());
VerifyOrQuit(!mode.IsRxOnWhenIdle());
VerifyOrQuit(!mode.IsFullThreadDevice());
VerifyOrQuit(mode.GetNetworkDataType() == NetworkData::kFullSet);
VerifyOrQuit(!mode.IsMinimalEndDevice());
//- - - - - - - - - - - - - - - - - - - - - - - -
// MED (stable subset netdata)
config.mRxOnWhenIdle = true;
config.mDeviceType = false;
config.mNetworkData = false;
mode.Set(config);
mode.Get(readConfig);
VerifyOrQuit(readConfig.mRxOnWhenIdle);
VerifyOrQuit(!readConfig.mDeviceType);
VerifyOrQuit(!readConfig.mNetworkData);
VerifyOrQuit(mode.IsValid());
VerifyOrQuit(mode.IsRxOnWhenIdle());
VerifyOrQuit(!mode.IsFullThreadDevice());
VerifyOrQuit(mode.GetNetworkDataType() == NetworkData::kStableSubset);
VerifyOrQuit(mode.IsMinimalEndDevice());
//- - - - - - - - - - - - - - - - - - - - - - - -
// MED (full set netdata)
config.mRxOnWhenIdle = true;
config.mDeviceType = false;
config.mNetworkData = true;
mode.Set(config);
mode.Get(readConfig);
VerifyOrQuit(readConfig.mRxOnWhenIdle);
VerifyOrQuit(!readConfig.mDeviceType);
VerifyOrQuit(readConfig.mNetworkData);
VerifyOrQuit(mode.IsValid());
VerifyOrQuit(mode.IsRxOnWhenIdle());
VerifyOrQuit(!mode.IsFullThreadDevice());
VerifyOrQuit(mode.GetNetworkDataType() == NetworkData::kFullSet);
VerifyOrQuit(mode.IsMinimalEndDevice());
//- - - - - - - - - - - - - - - - - - - - - - - -
// FTD (stable subset netdata)
config.mRxOnWhenIdle = true;
config.mDeviceType = true;
config.mNetworkData = false;
mode.Set(config);
mode.Get(readConfig);
VerifyOrQuit(readConfig.mRxOnWhenIdle);
VerifyOrQuit(readConfig.mDeviceType);
VerifyOrQuit(!readConfig.mNetworkData);
VerifyOrQuit(mode.IsValid());
VerifyOrQuit(mode.IsRxOnWhenIdle());
VerifyOrQuit(mode.IsFullThreadDevice());
VerifyOrQuit(mode.GetNetworkDataType() == NetworkData::kStableSubset);
VerifyOrQuit(!mode.IsMinimalEndDevice());
//- - - - - - - - - - - - - - - - - - - - - - - -
// FTD (full set netdata)
config.mRxOnWhenIdle = true;
config.mDeviceType = true;
config.mNetworkData = true;
mode.Set(config);
mode.Get(readConfig);
VerifyOrQuit(readConfig.mRxOnWhenIdle);
VerifyOrQuit(readConfig.mDeviceType);
VerifyOrQuit(readConfig.mNetworkData);
VerifyOrQuit(mode.IsValid());
VerifyOrQuit(mode.IsRxOnWhenIdle());
VerifyOrQuit(mode.IsFullThreadDevice());
VerifyOrQuit(mode.GetNetworkDataType() == NetworkData::kFullSet);
VerifyOrQuit(!mode.IsMinimalEndDevice());
//- - - - - - - - - - - - - - - - - - - - - - - -
// Invalid
config.mRxOnWhenIdle = false;
config.mDeviceType = true;
config.mNetworkData = true;
mode.Set(config);
mode.Get(readConfig);
VerifyOrQuit(!readConfig.mRxOnWhenIdle);
VerifyOrQuit(readConfig.mDeviceType);
VerifyOrQuit(readConfig.mNetworkData);
VerifyOrQuit(!mode.IsValid());
//- - - - - - - - - - - - - - - - - - - - - - - -
// Invalid
config.mRxOnWhenIdle = false;
config.mDeviceType = true;
config.mNetworkData = false;
mode.Set(config);
mode.Get(readConfig);
VerifyOrQuit(!readConfig.mRxOnWhenIdle);
VerifyOrQuit(readConfig.mDeviceType);
VerifyOrQuit(!readConfig.mNetworkData);
VerifyOrQuit(!mode.IsValid());
printf("TestDeviceMode passed\n");
}
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE
void TestDefaultDeviceProperties(void)
{
Instance *instance;
const otDeviceProperties *props;
uint8_t weight;
instance = static_cast<Instance *>(testInitInstance());
VerifyOrQuit(instance != nullptr);
props = otThreadGetDeviceProperties(instance);
VerifyOrQuit(props->mPowerSupply == OPENTHREAD_CONFIG_DEVICE_POWER_SUPPLY);
VerifyOrQuit(!props->mSupportsCcm);
VerifyOrQuit(!props->mIsUnstable);
VerifyOrQuit(props->mLeaderWeightAdjustment == OPENTHREAD_CONFIG_MLE_DEFAULT_LEADER_WEIGHT_ADJUSTMENT);
#if OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
VerifyOrQuit(props->mIsBorderRouter);
#else
VerifyOrQuit(!props->mIsBorderRouter);
#endif
weight = 64;
switch (props->mPowerSupply)
{
case OT_POWER_SUPPLY_BATTERY:
weight -= 8;
break;
case OT_POWER_SUPPLY_EXTERNAL:
break;
case OT_POWER_SUPPLY_EXTERNAL_STABLE:
weight += 4;
break;
case OT_POWER_SUPPLY_EXTERNAL_UNSTABLE:
weight -= 4;
break;
}
weight += props->mIsBorderRouter ? 1 : 0;
VerifyOrQuit(otThreadGetLocalLeaderWeight(instance) == weight);
printf("TestDefaultDeviceProperties passed\n");
}
void CompareDevicePropertiess(const otDeviceProperties &aFirst, const otDeviceProperties &aSecond)
{
static constexpr int8_t kMinAdjustment = -16;
static constexpr int8_t kMaxAdjustment = +16;
VerifyOrQuit(aFirst.mPowerSupply == aSecond.mPowerSupply);
VerifyOrQuit(aFirst.mIsBorderRouter == aSecond.mIsBorderRouter);
VerifyOrQuit(aFirst.mSupportsCcm == aSecond.mSupportsCcm);
VerifyOrQuit(aFirst.mIsUnstable == aSecond.mIsUnstable);
VerifyOrQuit(Clamp(aFirst.mLeaderWeightAdjustment, kMinAdjustment, kMaxAdjustment) ==
Clamp(aSecond.mLeaderWeightAdjustment, kMinAdjustment, kMaxAdjustment));
}
void TestLeaderWeightCalculation(void)
{
struct TestCase
{
otDeviceProperties mDeviceProperties;
uint8_t mExpectedLeaderWeight;
};
static const TestCase kTestCases[] = {
{{OT_POWER_SUPPLY_BATTERY, false, false, false, 0}, 56},
{{OT_POWER_SUPPLY_EXTERNAL, false, false, false, 0}, 64},
{{OT_POWER_SUPPLY_EXTERNAL_STABLE, false, false, false, 0}, 68},
{{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, false, false, false, 0}, 60},
{{OT_POWER_SUPPLY_BATTERY, true, false, false, 0}, 57},
{{OT_POWER_SUPPLY_EXTERNAL, true, false, false, 0}, 65},
{{OT_POWER_SUPPLY_EXTERNAL_STABLE, true, false, false, 0}, 69},
{{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, true, false, false, 0}, 61},
{{OT_POWER_SUPPLY_BATTERY, true, true, false, 0}, 64},
{{OT_POWER_SUPPLY_EXTERNAL, true, true, false, 0}, 72},
{{OT_POWER_SUPPLY_EXTERNAL_STABLE, true, true, false, 0}, 76},
{{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, true, true, false, 0}, 68},
// Check when `mIsUnstable` is set.
{{OT_POWER_SUPPLY_BATTERY, false, false, true, 0}, 56},
{{OT_POWER_SUPPLY_EXTERNAL, false, false, true, 0}, 60},
{{OT_POWER_SUPPLY_EXTERNAL_STABLE, false, false, true, 0}, 64},
{{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, false, false, true, 0}, 60},
{{OT_POWER_SUPPLY_BATTERY, true, false, true, 0}, 57},
{{OT_POWER_SUPPLY_EXTERNAL, true, false, true, 0}, 61},
{{OT_POWER_SUPPLY_EXTERNAL_STABLE, true, false, true, 0}, 65},
{{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, true, false, true, 0}, 61},
// Include non-zero `mLeaderWeightAdjustment`.
{{OT_POWER_SUPPLY_BATTERY, true, false, false, 10}, 67},
{{OT_POWER_SUPPLY_EXTERNAL, true, false, false, 10}, 75},
{{OT_POWER_SUPPLY_EXTERNAL_STABLE, true, false, false, 10}, 79},
{{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, true, false, false, 10}, 71},
{{OT_POWER_SUPPLY_BATTERY, false, false, false, -10}, 46},
{{OT_POWER_SUPPLY_EXTERNAL, false, false, false, -10}, 54},
{{OT_POWER_SUPPLY_EXTERNAL_STABLE, false, false, false, -10}, 58},
{{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, false, false, false, -10}, 50},
// Use `mLeaderWeightAdjustment` larger than valid range
// Make sure it clamps to -16 and +16.
{{OT_POWER_SUPPLY_BATTERY, false, false, false, 20}, 72},
{{OT_POWER_SUPPLY_EXTERNAL, false, false, false, 20}, 80},
{{OT_POWER_SUPPLY_EXTERNAL_STABLE, false, false, false, 20}, 84},
{{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, false, false, false, 20}, 76},
{{OT_POWER_SUPPLY_BATTERY, true, false, false, -20}, 41},
{{OT_POWER_SUPPLY_EXTERNAL, true, false, false, -20}, 49},
{{OT_POWER_SUPPLY_EXTERNAL_STABLE, true, false, false, -20}, 53},
{{OT_POWER_SUPPLY_EXTERNAL_UNSTABLE, true, false, false, -20}, 45},
};
Instance *instance;
instance = static_cast<Instance *>(testInitInstance());
VerifyOrQuit(instance != nullptr);
for (const TestCase &testCase : kTestCases)
{
otThreadSetDeviceProperties(instance, &testCase.mDeviceProperties);
CompareDevicePropertiess(testCase.mDeviceProperties, *otThreadGetDeviceProperties(instance));
VerifyOrQuit(otThreadGetLocalLeaderWeight(instance) == testCase.mExpectedLeaderWeight);
}
printf("TestLeaderWeightCalculation passed\n");
}
#endif // #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE
} // namespace ot
int main(void)
{
ot::TestDeviceMode();
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MLE_DEVICE_PROPERTY_LEADER_WEIGHT_ENABLE
ot::TestDefaultDeviceProperties();
ot::TestLeaderWeightCalculation();
#endif
printf("All tests passed\n");
return 0;
}