mirror of
https://github.com/espressif/openthread.git
synced 2026-10-09 17:27:41 +00:00
[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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user