Files
Abtin Keshavarzian d132939de4 [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.
2026-07-14 13:28:06 -07:00

858 lines
29 KiB
C++

/*
* Copyright (c) 2026, 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 "common/code_utils.hpp"
#include "common/frame_builder.hpp"
#include "common/frame_data.hpp"
#include "mac/mac_ltvs.hpp"
#include "test_platform.h"
#include "test_util.hpp"
namespace ot {
namespace Mac {
void TestSingleLtvEncodingAndDecoding(void)
{
struct TestCase
{
uint8_t mType;
uint8_t mLength;
bool mShouldBePacked;
};
static const TestCase kTestCases[] = {
// Zero-length LTVs (len bit size = 1, available type bits = 7)
{0, 0, true},
{1, 0, true},
{5, 0, true},
{64, 0, true},
{126, 0, true},
{127, 0, true},
{128, 0, true},
{255, 0, true},
// Length 1 LTVs (len bit size = 1, available type bits = 7)
{0, 1, true},
{1, 1, true},
{3, 1, true},
{32, 1, true},
{126, 1, true},
{127, 1, false},
{128, 1, false},
{255, 1, false},
// Length 2 LTVs (bitSize = 2, available type bits = 6)
{1, 2, true},
{15, 2, true},
{31, 2, true},
{62, 2, true},
{63, 2, false},
{64, 2, false},
{255, 2, false},
// Length 3 LTVs (bitSize = 2, available type bits = 6)
{0, 3, true},
{62, 3, true},
{63, 3, false},
{64, 3, false},
{128, 3, false},
// Length 4 LTVs (bitSize = 3, available type bits = 5)
{7, 4, true},
{30, 4, true},
{31, 4, false},
{32, 4, false},
{100, 4, false},
// Length 7 LTVs (bitSize = 3, available type bits = 5)
{1, 7, true},
{30, 7, true},
{31, 7, false},
{32, 7, false},
// Length 15 LTVs (bitSize = 4, available type bits = 4)
{1, 15, true},
{7, 15, true},
{14, 15, true},
{15, 15, false},
{16, 15, false},
// Length 31 LTVs (bitSize = 5, available type bits = 3)
{1, 31, true},
{3, 31, true},
{6, 31, true},
{7, 31, false},
{15, 31, false},
// Length 32 LTVs (bitSize = 6, available type bits = 2)
{0, 32, true},
{1, 32, true},
{2, 32, true},
{3, 32, false},
{4, 32, false},
{70, 32, false},
// Length 64 LTVs (bitSize = 7, available type bits = 1)
{0, 64, true},
{1, 64, false},
{10, 64, false},
// Length 127 LTVs (bitSize = 7, available type bits = 1)
{0, 127, true},
{1, 127, false},
{127, 127, false},
// Large lengths (bitSize >= 8)
{0, 128, false},
{1, 128, false},
{10, 128, false},
{0, 240, false},
{0, 255, false},
{255, 255, false},
};
static constexpr uint16_t kMaxBytesToPrint = 7;
uint8_t buffer[512];
uint8_t valuePattern[256];
FrameBuilder builder;
printf("------------------------------------------------------------------------------\n");
printf("TestTwoLtvsEncodingAndDecoding\n");
printf("TestSingleLtvEncodingAndDecoding\n");
for (size_t i = 0; i < sizeof(valuePattern); i++)
{
valuePattern[i] = static_cast<uint8_t>(i & 0xff);
}
for (const TestCase &testCase : kTestCases)
{
Ltv::AppendInfo appendLtv;
Ltv::ParsedInfo parsedLtv;
FrameData frameData;
uint16_t expectedHeaderSize;
uint16_t expectedTotalSize;
// EncodeAndAppend
builder.Init(buffer, sizeof(buffer));
appendLtv.InitTypeLength(testCase.mType, testCase.mLength);
SuccessOrQuit(Ltv::EncodeAndAppend(&appendLtv, 1, builder));
VerifyOrQuit(appendLtv.GetType() == testCase.mType);
VerifyOrQuit(appendLtv.GetLength() == testCase.mLength);
expectedHeaderSize = testCase.mShouldBePacked ? 1 : 2;
expectedTotalSize = expectedHeaderSize + testCase.mLength;
VerifyOrQuit(builder.GetLength() == expectedTotalSize);
VerifyOrQuit(appendLtv.GetValue() != nullptr);
memcpy(appendLtv.GetValue(), valuePattern, testCase.mLength);
printf("type:%-3u length:%-3u -> %-6s encodedLen:%u [", testCase.mType, testCase.mLength,
testCase.mShouldBePacked ? "packed" : "base", builder.GetLength());
for (uint16_t i = 0; i < Min(builder.GetLength(), kMaxBytesToPrint); i++)
{
printf(" %02x", buffer[i]);
}
printf("%s ]\n", builder.GetLength() > kMaxBytesToPrint ? " ..." : "");
// ValidateAllIn
frameData.Init(buffer, builder.GetLength());
SuccessOrQuit(Ltv::ValidateAllIn(frameData));
// ParseFromAndAdvance
SuccessOrQuit(parsedLtv.ParseFromAndAdvance(frameData));
VerifyOrQuit(parsedLtv.GetType() == testCase.mType);
VerifyOrQuit(parsedLtv.GetLength() == testCase.mLength);
VerifyOrQuit(parsedLtv.GetValue() != nullptr);
VerifyOrQuit(memcmp(parsedLtv.GetValue(), valuePattern, testCase.mLength) == 0);
VerifyOrQuit(frameData.IsEmpty());
VerifyOrQuit(parsedLtv.ParseFromAndAdvance(frameData) == kErrorNotFound);
// FindIn
frameData.Init(buffer, builder.GetLength());
SuccessOrQuit(parsedLtv.FindIn(frameData, testCase.mType));
VerifyOrQuit(parsedLtv.GetType() == testCase.mType);
VerifyOrQuit(parsedLtv.GetLength() == testCase.mLength);
VerifyOrQuit(frameData.GetLength() == builder.GetLength());
}
printf("TestSingleLtvEncodingAndDecoding - PASSED\n");
}
void TestTwoLtvsEncodingAndDecoding(void)
{
struct TestCase
{
uint8_t mType;
uint8_t mLength;
};
static const TestCase kTestCases[] = {
// Zero length LTVs
{0, 0},
{1, 0},
{5, 0},
{64, 0},
{255, 0},
// Length 1 LTVs
{0, 1},
{1, 1},
{3, 1},
{126, 1},
{127, 1},
{255, 1},
// Length 2 LTVs
{1, 2},
{15, 2},
{62, 2},
{63, 2},
{255, 2},
// Length 3 LTVs
{0, 3},
{15, 3},
{62, 3},
{66, 3},
{129, 3},
// Length 5 LTVs
{1, 5},
{3, 5},
{7, 5},
{8, 5},
{17, 5},
{30, 5},
// Length 7 LTVs
{1, 7},
{30, 7},
{31, 7},
{32, 7},
// Length 13 LTVs
{0, 13},
{1, 13},
{3, 13},
{7, 13},
{8, 13},
{13, 13},
{15, 13},
{16, 13},
// Length 31 LTVs
{1, 31},
{6, 31},
{7, 31},
{15, 31},
// Misc
{0, 100},
{1, 100},
{5, 100},
{0, 200},
{1, 200},
{2, 200},
{8, 200},
};
static const uint8_t kSecondLtvSizes[] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 29, 30, 31,
32, 33, 34, 62, 63, 64, 65, 126, 127, 128, 129, 130, 200, 201, 202, 250, 253, 254, 255,
};
uint8_t buffer[512];
uint8_t valPattern1[256];
uint8_t valPattern2[256];
FrameBuilder builder;
printf("------------------------------------------------------------------------------\n");
printf("TestTwoLtvsEncodingAndDecoding\n");
for (size_t i = 0; i < sizeof(valPattern1); i++)
{
valPattern1[i] = static_cast<uint8_t>(i + 0x10);
valPattern2[i] = static_cast<uint8_t>(i + 0x80);
}
for (const TestCase &testCase : kTestCases)
{
for (uint8_t secondSize : kSecondLtvSizes)
{
Ltv::AppendInfo appendLtvs[2];
Ltv::ParsedInfo parsedLtv;
FrameData frameData;
uint8_t secondType;
uint8_t secondLength;
uint16_t firstSize;
if (secondSize == 1)
{
secondType = 1;
secondLength = 0;
}
else if (secondSize == 2)
{
secondType = 1;
secondLength = 1;
}
else
{
// Will use base (unpacked) format since type is 129 > 127
secondType = 129;
secondLength = secondSize - 2;
}
// EncodeAndAppend
builder.Init(buffer, sizeof(buffer));
appendLtvs[0].InitTypeLength(testCase.mType, testCase.mLength);
appendLtvs[1].InitTypeLength(secondType, secondLength);
SuccessOrQuit(Ltv::EncodeAndAppend(appendLtvs, 2, builder));
VerifyOrQuit(appendLtvs[0].GetType() == testCase.mType);
VerifyOrQuit(appendLtvs[0].GetLength() == testCase.mLength);
VerifyOrQuit(appendLtvs[0].GetValue() != nullptr);
memcpy(appendLtvs[0].GetValue(), valPattern1, testCase.mLength);
VerifyOrQuit(appendLtvs[1].GetType() == secondType);
VerifyOrQuit(appendLtvs[1].GetLength() == secondLength);
VerifyOrQuit(appendLtvs[1].GetValue() != nullptr);
memcpy(appendLtvs[1].GetValue(), valPattern2, secondLength);
VerifyOrQuit(builder.GetLength() > secondSize);
firstSize = builder.GetLength() - secondSize;
VerifyOrQuit(firstSize > testCase.mLength);
VerifyOrQuit((firstSize == testCase.mLength + 1) || (firstSize == testCase.mLength + 2));
fprintf(stderr, "type1:%-3u len1:%-3u | type2:%-3u len2:%-3u -> encoded-len:%-4u = [%u + %u]\n",
testCase.mType, testCase.mLength, secondType, secondLength, builder.GetLength(), firstSize,
secondSize);
// ValidateAllIn
frameData.Init(buffer, builder.GetLength());
SuccessOrQuit(Ltv::ValidateAllIn(frameData));
// Parse first LTV
SuccessOrQuit(parsedLtv.ParseFromAndAdvance(frameData));
VerifyOrQuit(parsedLtv.GetType() == testCase.mType);
VerifyOrQuit(parsedLtv.GetLength() == testCase.mLength);
VerifyOrQuit(parsedLtv.GetValue() != nullptr);
VerifyOrQuit(memcmp(parsedLtv.GetValue(), valPattern1, testCase.mLength) == 0);
// Parse second LTV
SuccessOrQuit(parsedLtv.ParseFromAndAdvance(frameData));
VerifyOrQuit(parsedLtv.GetType() == secondType);
VerifyOrQuit(parsedLtv.GetLength() == secondLength);
VerifyOrQuit(parsedLtv.GetValue() != nullptr);
VerifyOrQuit(memcmp(parsedLtv.GetValue(), valPattern2, secondLength) == 0);
VerifyOrQuit(frameData.IsEmpty());
VerifyOrQuit(parsedLtv.ParseFromAndAdvance(frameData) == kErrorNotFound);
frameData.Init(buffer, builder.GetLength());
// Test FindIn for first LTV
SuccessOrQuit(parsedLtv.FindIn(frameData, testCase.mType));
VerifyOrQuit(parsedLtv.GetType() == testCase.mType);
VerifyOrQuit(parsedLtv.GetLength() == testCase.mLength);
if (secondType != testCase.mType)
{
// Test FindIn for second LTV type
SuccessOrQuit(parsedLtv.FindIn(frameData, secondType));
VerifyOrQuit(parsedLtv.GetType() == secondType);
VerifyOrQuit(parsedLtv.GetLength() == secondLength);
}
}
}
printf("TestTwoLtvsEncodingAndDecoding - PASSED\n");
}
void TestMultipleLtvsAndFinding(void)
{
const uint8_t kType1 = 1;
const uint8_t kType2 = 2;
const uint8_t kType3 = 3;
const uint8_t kTypeNonExistent = 99;
const uint8_t kPatternA[] = {0xaa, 0xaa};
const uint8_t kPatternB[] = {0xbb, 0xbb, 0xbb};
const uint8_t kPatternC[] = {0xcc, 0xcc, 0xcc, 0xcc, 0xcc};
const uint8_t kPatternD[] = {0xdd};
const uint8_t kPatternF[] = {0xff, 0xff, 0xff, 0xff};
uint8_t buffer[256];
FrameBuilder builder;
Ltv::AppendInfo appendLtvs[6];
Ltv::ParsedInfo parsedLtv;
FrameData frameData;
printf("------------------------------------------------------------------------------\n");
printf("TestMultipleLtvsAndFinding\n");
builder.Init(buffer, sizeof(buffer));
// Initialize 6 LTVs:
// LTV 0: Type 1, Length 2 (kPatternA)
// LTV 1: Type 2, Length 3 (kPatternB)
// LTV 2: Type 1, Length 5 (kPatternC)
// LTV 3: Type 3, Length 1 (kPatternD)
// LTV 4: Type 1, Length 0 (Empty)
// LTV 5: Type 2, Length 4 (kPatternF)
appendLtvs[0].InitTypeLengthId(kType1, sizeof(kPatternA), 0);
appendLtvs[1].InitTypeLengthId(kType2, sizeof(kPatternB), 1);
appendLtvs[2].InitTypeLengthId(kType1, sizeof(kPatternC), 2);
appendLtvs[3].InitTypeLengthId(kType3, sizeof(kPatternD), 3);
appendLtvs[4].InitTypeLengthId(kType1, 0, 4);
appendLtvs[5].InitTypeLengthId(kType2, sizeof(kPatternF), 5);
SuccessOrQuit(Ltv::EncodeAndAppend(appendLtvs, 6, builder));
memcpy(appendLtvs[0].GetValue(), kPatternA, sizeof(kPatternA));
memcpy(appendLtvs[1].GetValue(), kPatternB, sizeof(kPatternB));
memcpy(appendLtvs[2].GetValue(), kPatternC, sizeof(kPatternC));
memcpy(appendLtvs[3].GetValue(), kPatternD, sizeof(kPatternD));
memcpy(appendLtvs[5].GetValue(), kPatternF, sizeof(kPatternF));
// Validate overall buffer
frameData.Init(buffer, builder.GetLength());
SuccessOrQuit(Ltv::ValidateAllIn(frameData));
// --- Test FindIn (Non-advancing search) ---
// Repeated FindIn(kType1) must always return the first instance (LTV 0)
frameData.Init(buffer, builder.GetLength());
for (int i = 0; i < 3; i++)
{
SuccessOrQuit(parsedLtv.FindIn(frameData, kType1));
VerifyOrQuit(parsedLtv.GetType() == kType1);
VerifyOrQuit(parsedLtv.GetLength() == sizeof(kPatternA));
VerifyOrQuit(memcmp(parsedLtv.GetValue(), kPatternA, sizeof(kPatternA)) == 0);
VerifyOrQuit(frameData.GetLength() == builder.GetLength());
}
// Repeated FindIn(kType2) must always return the first instance (LTV 1)
for (int i = 0; i < 3; i++)
{
SuccessOrQuit(parsedLtv.FindIn(frameData, kType2));
VerifyOrQuit(parsedLtv.GetType() == kType2);
VerifyOrQuit(parsedLtv.GetLength() == sizeof(kPatternB));
VerifyOrQuit(memcmp(parsedLtv.GetValue(), kPatternB, sizeof(kPatternB)) == 0);
VerifyOrQuit(frameData.GetLength() == builder.GetLength());
}
// FindIn for non-existent type returns kErrorNotFound without modifying frameData
VerifyOrQuit(parsedLtv.FindIn(frameData, kTypeNonExistent) == kErrorNotFound);
VerifyOrQuit(frameData.GetLength() == builder.GetLength());
// --- Test FindInAndAdvance (Advancing search to find all instances) ---
// Iterate through all instances of kType1
frameData.Init(buffer, builder.GetLength());
// 1st instance of kType1 (LTV 0)
SuccessOrQuit(parsedLtv.FindInAndAdvance(frameData, kType1));
VerifyOrQuit(parsedLtv.GetType() == kType1);
VerifyOrQuit(parsedLtv.GetLength() == sizeof(kPatternA));
VerifyOrQuit(memcmp(parsedLtv.GetValue(), kPatternA, sizeof(kPatternA)) == 0);
// 2nd instance of kType1 (LTV 2)
SuccessOrQuit(parsedLtv.FindInAndAdvance(frameData, kType1));
VerifyOrQuit(parsedLtv.GetType() == kType1);
VerifyOrQuit(parsedLtv.GetLength() == sizeof(kPatternC));
VerifyOrQuit(memcmp(parsedLtv.GetValue(), kPatternC, sizeof(kPatternC)) == 0);
// 3rd instance of kType1 (LTV 4, length 0)
SuccessOrQuit(parsedLtv.FindInAndAdvance(frameData, kType1));
VerifyOrQuit(parsedLtv.GetType() == kType1);
VerifyOrQuit(parsedLtv.GetLength() == 0);
// 4th search for kType1 returns kErrorNotFound
VerifyOrQuit(parsedLtv.FindInAndAdvance(frameData, kType1) == kErrorNotFound);
// Iterate through all instances of kType2
frameData.Init(buffer, builder.GetLength());
// 1st instance of kType2 (LTV 1)
SuccessOrQuit(parsedLtv.FindInAndAdvance(frameData, kType2));
VerifyOrQuit(parsedLtv.GetType() == kType2);
VerifyOrQuit(parsedLtv.GetLength() == sizeof(kPatternB));
VerifyOrQuit(memcmp(parsedLtv.GetValue(), kPatternB, sizeof(kPatternB)) == 0);
// 2nd instance of kType2 (LTV 5)
SuccessOrQuit(parsedLtv.FindInAndAdvance(frameData, kType2));
VerifyOrQuit(parsedLtv.GetType() == kType2);
VerifyOrQuit(parsedLtv.GetLength() == sizeof(kPatternF));
VerifyOrQuit(memcmp(parsedLtv.GetValue(), kPatternF, sizeof(kPatternF)) == 0);
// 3rd search for kType2 returns kErrorNotFound
VerifyOrQuit(parsedLtv.FindInAndAdvance(frameData, kType2) == kErrorNotFound);
// Mixed search: find kType3, then search remaining buffer for kType1 and kType2
frameData.Init(buffer, builder.GetLength());
SuccessOrQuit(parsedLtv.FindInAndAdvance(frameData, kType3));
VerifyOrQuit(parsedLtv.GetType() == kType3);
VerifyOrQuit(parsedLtv.GetLength() == sizeof(kPatternD));
SuccessOrQuit(parsedLtv.FindInAndAdvance(frameData, kType1));
VerifyOrQuit(parsedLtv.GetType() == kType1);
VerifyOrQuit(parsedLtv.GetLength() == 0);
SuccessOrQuit(parsedLtv.FindInAndAdvance(frameData, kType2));
VerifyOrQuit(parsedLtv.GetType() == kType2);
VerifyOrQuit(parsedLtv.GetLength() == sizeof(kPatternF));
VerifyOrQuit(frameData.IsEmpty());
printf("TestMultipleLtvsAndFinding - PASSED\n");
}
void TestLtvErrorsAndNegativeCases(void)
{
uint8_t buffer[256];
uint8_t smallBuffer[2];
FrameBuilder builder;
Ltv::AppendInfo appendLtv;
Ltv::ParsedInfo parsedLtv;
FrameData frameData;
printf("------------------------------------------------------------------------------\n");
printf("TestLtvErrorsAndNegativeCases\n");
// --- EncodeAndAppend buffer overflow (kErrorNoBufs) ---
// Trying to append a 10-byte LTV into a 2-byte FrameBuilder buffer
builder.Init(smallBuffer, sizeof(smallBuffer));
appendLtv.InitTypeLength(/* aType */ 1, /* aLength */ 10);
VerifyOrQuit(Ltv::EncodeAndAppend(&appendLtv, 1, builder) == kErrorNoBufs);
// --- Truncated LTV payload during parsing (kErrorParse) ---
// Encode a valid LTV with Length = 20
builder.Init(buffer, sizeof(buffer));
appendLtv.InitTypeLength(/* aType */ 1, /* aLength */ 20);
SuccessOrQuit(Ltv::EncodeAndAppend(&appendLtv, 1, builder));
// Provide a truncated FrameData buffer (header + only 19 payload bytes instead of 20)
frameData.Init(buffer, builder.GetLength() - 1);
VerifyOrQuit(parsedLtv.ParseFromAndAdvance(frameData) == kErrorParse);
frameData.Init(buffer, builder.GetLength() - 1);
VerifyOrQuit(Ltv::ValidateAllIn(frameData) == kErrorParse);
printf("TestLtvErrorsAndNegativeCases - PASSED\n");
}
void TestAppendInfoGetId(void)
{
uint8_t buffer[256];
FrameBuilder builder;
Ltv::AppendInfo appendLtvs[3];
Ltv::AppendInfo defaultInfo;
FrameData frameData;
printf("------------------------------------------------------------------------------\n");
printf("TestAppendInfoGetId\n");
// Default InitTypeLength(aType, aLength) sets mId to 0
defaultInfo.InitTypeLength(/* aType */ 1, /* aLength */ 2);
VerifyOrQuit(defaultInfo.GetId() == 0);
// Init with explicit aId
appendLtvs[0].InitTypeLengthId(/* aType */ 1, /* aLength */ 2, /* aId */ 0x101);
appendLtvs[1].InitTypeLengthId(/* aType */ 1, /* aLength */ 4, /* aId */ 0x102);
appendLtvs[2].InitTypeLengthId(/* aType */ 2, /* aLength */ 1, /* aId */ 0x201);
VerifyOrQuit(appendLtvs[0].GetId() == 0x101);
VerifyOrQuit(appendLtvs[1].GetId() == 0x102);
VerifyOrQuit(appendLtvs[2].GetId() == 0x201);
builder.Init(buffer, sizeof(buffer));
SuccessOrQuit(Ltv::EncodeAndAppend(appendLtvs, 3, builder));
// Verify mId is preserved after EncodeAndAppend
VerifyOrQuit(appendLtvs[0].GetId() == 0x101);
VerifyOrQuit(appendLtvs[1].GetId() == 0x102);
VerifyOrQuit(appendLtvs[2].GetId() == 0x201);
// Verify caller can locate AppendInfo entries by ID
for (Ltv::AppendInfo &ltv : appendLtvs)
{
if (ltv.GetId() == 0x101)
{
VerifyOrQuit(ltv.GetType() == 1 && ltv.GetLength() == 2);
}
else if (ltv.GetId() == 0x102)
{
VerifyOrQuit(ltv.GetType() == 1 && ltv.GetLength() == 4);
}
else if (ltv.GetId() == 0x201)
{
VerifyOrQuit(ltv.GetType() == 2 && ltv.GetLength() == 1);
}
else
{
VerifyOrQuit(false);
}
}
frameData.Init(buffer, builder.GetLength());
SuccessOrQuit(Ltv::ValidateAllIn(frameData));
printf("TestAppendInfoGetId - PASSED\n");
}
void TestFindAndValidate(void)
{
uint8_t buffer[256];
FrameBuilder builder;
Ltv::AppendInfo appendLtvs[3];
Ltv::ParsedInfo parsedLtv;
FrameData frameData;
uint8_t targetId[] = {0x12, 0x34, 0x56};
printf("------------------------------------------------------------------------------\n");
printf("TestFindAndValidate\n");
builder.Init(buffer, sizeof(buffer));
// LTV 0: Type = TargetIdLtv::kType (1), Length = 3 (Valid TargetIdLtv)
// LTV 1: Type = 2, Length = 4
// LTV 2: Type = TargetIdLtv::kType (1), Length = 0 (Invalid TargetIdLtv, length 0 < kMinLength 1)
appendLtvs[0].InitAsWithLength<TargetIdLtv>(sizeof(targetId));
appendLtvs[1].InitTypeLength(2, 4);
appendLtvs[2].InitAsWithLength<TargetIdLtv>(0);
SuccessOrQuit(Ltv::EncodeAndAppend(appendLtvs, 3, builder));
memcpy(appendLtvs[0].GetValue(), targetId, sizeof(targetId));
// -- Test `ValidateAs<>` along with `ParseFromAndAdvance` --
frameData.Init(buffer, builder.GetLength());
// LTV 0 is a valid TargetIdLtv
SuccessOrQuit(parsedLtv.ParseFromAndAdvance(frameData));
SuccessOrQuit(parsedLtv.ValidateAs<TargetIdLtv>());
// LTV 1 has different type (Type 2), so ValidateAs returns kErrorParse
SuccessOrQuit(parsedLtv.ParseFromAndAdvance(frameData));
VerifyOrQuit(parsedLtv.ValidateAs<TargetIdLtv>() == kErrorParse);
// LTV 2 has matching type (TargetIdLtv::kType) but invalid length (0), so ValidateAs returns kErrorParse
SuccessOrQuit(parsedLtv.ParseFromAndAdvance(frameData));
VerifyOrQuit(parsedLtv.ValidateAs<TargetIdLtv>() == kErrorParse);
// -- Test `FindAndValidate` on valid first TargetIdLtv --
frameData.Init(buffer, builder.GetLength());
SuccessOrQuit(parsedLtv.FindAndValidate<TargetIdLtv>(frameData));
VerifyOrQuit(parsedLtv.GetType() == TargetIdLtv::kType);
VerifyOrQuit(parsedLtv.GetLength() == sizeof(targetId));
VerifyOrQuit(memcmp(parsedLtv.GetValue(), targetId, sizeof(targetId)) == 0);
VerifyOrQuit(frameData.GetLength() == builder.GetLength()); // Not Advanced
// -- Test `FindValidateAndAdvance` --
frameData.Init(buffer, builder.GetLength());
SuccessOrQuit(parsedLtv.FindValidateAndAdvance<TargetIdLtv>(frameData));
VerifyOrQuit(parsedLtv.GetType() == TargetIdLtv::kType);
VerifyOrQuit(parsedLtv.GetLength() == sizeof(targetId));
VerifyOrQuit(frameData.GetLength() < builder.GetLength());
// Next search for TargetIdLtv finds LTV 2 (Length 0) which should fail validation
VerifyOrQuit(parsedLtv.FindAndValidate<TargetIdLtv>(frameData) == kErrorParse);
VerifyOrQuit(parsedLtv.FindValidateAndAdvance<TargetIdLtv>(frameData) == kErrorParse);
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
int main(void)
{
ot::Mac::TestSingleLtvEncodingAndDecoding();
ot::Mac::TestTwoLtvsEncodingAndDecoding();
ot::Mac::TestMultipleLtvsAndFinding();
ot::Mac::TestLtvErrorsAndNegativeCases();
ot::Mac::TestAppendInfoGetId();
ot::Mac::TestFindAndValidate();
ot::Mac::TestOptimizeListOrder();
printf("\nAll tests passed\n");
return 0;
}