mirror of
https://github.com/espressif/openthread.git
synced 2026-09-19 23:47:33 +00:00
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.
858 lines
29 KiB
C++
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 <v : 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;
|
|
}
|