diff --git a/src/core/mac/mac_ltvs.cpp b/src/core/mac/mac_ltvs.cpp index 0f68c18fc..e710dc91b 100644 --- a/src/core/mac/mac_ltvs.cpp +++ b/src/core/mac/mac_ltvs.cpp @@ -34,6 +34,7 @@ #include "mac_ltvs.hpp" #include "common/bit_utils.hpp" +#include "common/numeric_limits.hpp" namespace ot { namespace Mac { @@ -270,5 +271,79 @@ exit: return error; } +void Ltv::OptimizeListOrder(AppendInfo *aLtvList, uint16_t aNumLtvs) +{ + uint32_t curLength = 0; + + VerifyOrExit(aNumLtvs > 1); + + // We determine the optimal order backward from the last LTV position + // (`aNumLtvs - 1`) down to the first (`0`). At each position `curLtv`, + // we evaluate all candidate entries from `aLtvList[0]` up to `curLtv` + // given the cumulative length `curLength` of the entries already placed + // after `curLtv`. We select the candidate that can be packed and yields + // the smallest cumulative length, placing it into `curLtv`. + + for (AppendInfo *curLtv = &aLtvList[aNumLtvs - 1]; curLtv > &aLtvList[0]; curLtv--) + { + AppendInfo *bestLtv = nullptr; + uint32_t bestLength = NumericLimits::kMax; + + for (AppendInfo *ltv = &aLtvList[0]; ltv <= curLtv; ltv++) + { + uint32_t length = curLength; + bool isBetter = true; + + ltv->DetermineIfPackable(length); + + // We compare candidate `ltv` against the current `bestLtv`: + // + // - If `bestLtv` is packable (`bestLtv->mIsPackable` is true), + // candidate `ltv` is preferred only if it is also packable and + // yields a smaller or equal cumulative length (`length <= + // bestLength`). Using `<=` prefers later entries on ties, + // preserving the caller's original list order. + // + // - Otherwise (`bestLtv` is null or unpackable), we always prefer + // the new candidate `ltv`: If `ltv` is packable, it improves + // packability; if `ltv` is also unpackable, preferring a later + // candidate preserves the caller's original list order. + + if ((bestLtv != nullptr) && bestLtv->mIsPackable) + { + isBetter = ltv->mIsPackable && (length <= bestLength); + } + + if (isBetter) + { + bestLtv = ltv; + bestLength = length; + } + } + + // Move `bestLtv` to position `curLtv` by shifting the elements + // between `bestLtv + 1` and `curLtv` one position up. Using a shift + // rather than a direct swap preserves the relative order of all + // remaining entries. + + if ((bestLtv != nullptr) && (bestLtv != curLtv)) + { + AppendInfo best = *bestLtv; + + for (AppendInfo *ltv = bestLtv; ltv < curLtv; ltv++) + { + *ltv = ltv[1]; + } + + *curLtv = best; + } + + curLength = bestLength; + } + +exit: + return; +} + } // namespace Mac } // namespace ot diff --git a/src/core/mac/mac_ltvs.hpp b/src/core/mac/mac_ltvs.hpp index 8a91a6781..0d4d54125 100644 --- a/src/core/mac/mac_ltvs.hpp +++ b/src/core/mac/mac_ltvs.hpp @@ -343,6 +343,17 @@ public: */ static Error EncodeAndAppend(AppendInfo *aLtvList, uint16_t aNumLtvs, FrameBuilder &aBuilder); + /** + * Optimizes the order of LTV entries in an array to minimize the overall encoded length. + * + * This method rearranges the entries in @p aLtvList so that as many LTVs as possible can use the packed format + * rather than the base header format. + * + * @param[in,out] aLtvList An array of `AppendInfo` entries to reorder. + * @param[in] aNumLtvs The number of LTV elements in @p aLtvList. + */ + static void OptimizeListOrder(AppendInfo *aLtvList, uint16_t aNumLtvs); + private: Ltv(void) = delete; }; diff --git a/tests/unit/test_ltv.cpp b/tests/unit/test_ltv.cpp index 3acf734c1..5aa97309f 100644 --- a/tests/unit/test_ltv.cpp +++ b/tests/unit/test_ltv.cpp @@ -718,6 +718,127 @@ void TestFindAndValidate(void) printf("TestFindAndValidate - PASSED\n"); } +void PrintLtvList(const char *aLabel, const Ltv::AppendInfo *aLtvList, uint16_t aNumLtvs) +{ + printf("\n%s:\n", aLabel); + + for (uint16_t i = 0; i < aNumLtvs; i++) + { + printf(" [%u] type:%-3u len:%-3u id:%u\n", i, aLtvList[i].GetType(), aLtvList[i].GetLength(), + aLtvList[i].GetId()); + } +} + +void TestOptimizeListOrder(void) +{ + Ltv::AppendInfo ltvs[8]; + uint8_t buffer[512]; + FrameBuilder builder; + uint16_t unoptimizedLen; + uint16_t optimizedLen; + + printf("------------------------------------------------------------------------------\n"); + printf("TestOptimizeListOrder\n"); + + // Single LTV (No-op edge case) + ltvs[0].InitTypeLengthId(1, 4, 100); + Ltv::OptimizeListOrder(ltvs, 1); + VerifyOrQuit(ltvs[0].GetId() == 100); + + // Equal packability and length (Order preservation test) + ltvs[0].InitTypeLengthId(1, 2, 10); + ltvs[1].InitTypeLengthId(2, 2, 20); + ltvs[2].InitTypeLengthId(3, 2, 30); + + Ltv::OptimizeListOrder(ltvs, 3); + VerifyOrQuit(ltvs[0].GetId() == 10); + VerifyOrQuit(ltvs[1].GetId() == 20); + VerifyOrQuit(ltvs[2].GetId() == 30); + + // Mixed lengths and types where reordering reduces overall encoded length + ltvs[0].InitTypeLengthId(1, 0, 0xa); + ltvs[1].InitTypeLengthId(2, 4, 0xb); + ltvs[2].InitTypeLengthId(2, 3, 0xc); + ltvs[3].InitTypeLengthId(3, 90, 0xd); + + builder.Init(buffer, sizeof(buffer)); + SuccessOrQuit(Ltv::EncodeAndAppend(ltvs, 4, builder)); + unoptimizedLen = builder.GetLength(); + + PrintLtvList("Unoptimized", ltvs, 4); + + Ltv::OptimizeListOrder(ltvs, 4); + + PrintLtvList("Optimized", ltvs, 4); + + VerifyOrQuit(ltvs[0].GetId() == 0xd); + VerifyOrQuit(ltvs[1].GetId() == 0xb); + VerifyOrQuit(ltvs[2].GetId() == 0xc); + VerifyOrQuit(ltvs[3].GetId() == 0xa); + + builder.Init(buffer, sizeof(buffer)); + SuccessOrQuit(Ltv::EncodeAndAppend(ltvs, 4, builder)); + optimizedLen = builder.GetLength(); + + printf("\nEncoded-len: Unoptimized %lu -> Optimized %lu\n", ToUlong(unoptimizedLen), ToUlong(optimizedLen)); + + VerifyOrQuit(optimizedLen < unoptimizedLen); + + // Optimizing again, should not change the order + + Ltv::OptimizeListOrder(ltvs, 4); + + VerifyOrQuit(ltvs[0].GetId() == 0xd); + VerifyOrQuit(ltvs[1].GetId() == 0xb); + VerifyOrQuit(ltvs[2].GetId() == 0xc); + VerifyOrQuit(ltvs[3].GetId() == 0xa); + + printf("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n"); + + ltvs[0].InitTypeLengthId(5, 0, 10); + ltvs[1].InitTypeLengthId(255, 0, 20); + ltvs[2].InitTypeLengthId(127, 1, 30); + ltvs[3].InitTypeLengthId(1, 30, 40); + ltvs[4].InitTypeLengthId(1, 14, 50); + ltvs[5].InitTypeLengthId(128, 100, 60); + + builder.Init(buffer, sizeof(buffer)); + SuccessOrQuit(Ltv::EncodeAndAppend(ltvs, 6, builder)); + unoptimizedLen = builder.GetLength(); + + PrintLtvList("Unoptimized", ltvs, 6); + + Ltv::OptimizeListOrder(ltvs, 6); + + PrintLtvList("Optimized", ltvs, 6); + + VerifyOrQuit(ltvs[0].GetId() == 30); + VerifyOrQuit(ltvs[1].GetId() == 60); + VerifyOrQuit(ltvs[2].GetId() == 40); + VerifyOrQuit(ltvs[3].GetId() == 50); + VerifyOrQuit(ltvs[4].GetId() == 10); + VerifyOrQuit(ltvs[5].GetId() == 20); + + builder.Init(buffer, sizeof(buffer)); + SuccessOrQuit(Ltv::EncodeAndAppend(ltvs, 6, builder)); + optimizedLen = builder.GetLength(); + + printf("\nEncoded-len: Unoptimized %lu -> Optimized %lu\n", ToUlong(unoptimizedLen), ToUlong(optimizedLen)); + + VerifyOrQuit(optimizedLen < unoptimizedLen); + + Ltv::OptimizeListOrder(ltvs, 6); + + VerifyOrQuit(ltvs[0].GetId() == 30); + VerifyOrQuit(ltvs[1].GetId() == 60); + VerifyOrQuit(ltvs[2].GetId() == 40); + VerifyOrQuit(ltvs[3].GetId() == 50); + VerifyOrQuit(ltvs[4].GetId() == 10); + VerifyOrQuit(ltvs[5].GetId() == 20); + + printf("\nTestOptimizeListOrder - PASSED\n"); +} + } // namespace Mac } // namespace ot @@ -729,6 +850,7 @@ int main(void) ot::Mac::TestLtvErrorsAndNegativeCases(); ot::Mac::TestAppendInfoGetId(); ot::Mac::TestFindAndValidate(); + ot::Mac::TestOptimizeListOrder(); printf("\nAll tests passed\n"); return 0;