[mac-ltv] add Ltv::OptimizeListOrder() helper method (#13309)

This commit adds `Ltv::OptimizeListOrder()` to optimize the ordering
of LTV entries in an array before encoding, minimizing the overall
encoded length in a Thread Header IE.

When encoding LTVs, entries located closer to the end of the list have
a smaller cumulative remaining length. A smaller remaining length
requires fewer bits for the length field, leaving more bits available
for the type field and allowing the entry to use the 1-byte packed
header format rather than the 2-byte base header format.

Key details of `Ltv::OptimizeListOrder()`:
- Evaluates positions backward from the last entry (`aNumLtvs - 1`)
  down to the first (`0`).
- At each position `curLtv`, evaluates all candidate entries from
  `aLtvList[0]` through `curLtv` against the cumulative length
  `curLength` of entries already placed after `curLtv`.
- Selects the candidate that can use the packed format and yields the
  smallest cumulative length.
- Order Preservation: On ties (`length <= bestLength` for packable
  entries, or when `bestLtv` is unpackable), prioritizes later
  candidates in the list to preserve the caller's original relative
  list order.
- In-place Shift: Places the selected candidate at `curLtv` by
  shifting elements between `bestLtv + 1` and `curLtv` left by one
  position, preserving the relative order of remaining unplaced
  candidate entries.

Also adds a details unit test (`TestOptimizeListOrder`) in
`test_ltv.cpp` covering single-entry no-ops, order preservation on
ties, mixed length and type LTV optimization.
This commit is contained in:
Abtin Keshavarzian
2026-07-14 13:28:06 -07:00
committed by GitHub
parent 1b9c862cb3
commit d132939de4
3 changed files with 208 additions and 0 deletions
+75
View File
@@ -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<uint32_t>::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
+11
View File
@@ -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;
};
+122
View File
@@ -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;