mirror of
https://github.com/espressif/openthread.git
synced 2026-09-21 16:37:37 +00:00
[mac] implement LTV parsing and encoding (#13297)
This commit adds support for parsing, searching, and encoding Thread Header IE Length-Type-Value (LTV) elements. Key changes introduced: - `Ltv::ParsedInfo`: Class for parsing and searching LTV elements from a `FrameData` buffer. Automatically handles packed and base (unpacked) formats. Supports non-advancing (`FindIn`, `FindAndValidate`) and advancing (`ParseFromAndAdvance`, `FindInAndAdvance`, `FindValidateAndAdvance`) operations. - `Ltv::AppendInfo`: Class for setting up LTV entries to encode and append. Provides various initializer methods (`InitTypeLength`, `InitTypeLengthId`, `InitAs<LtvType>`, `InitAsWithLength<LtvType`). - `Ltv::EncodeAndAppend()`: Encodes one or more LTVs into a `FrameBuilder`, automatically determining whether to use packed or base (unpacked) formats. - `TargetIdLtv`: Defines type constants and validation helpers for Target ID LTVs. - `test_ltv`: A detailed unit test covering single, double, multi-LTV parsing, type matching, error handling.
This commit is contained in:
@@ -536,6 +536,8 @@ openthread_core_files = [
|
||||
"mac/mac_header_ie.hpp",
|
||||
"mac/mac_links.cpp",
|
||||
"mac/mac_links.hpp",
|
||||
"mac/mac_ltvs.cpp",
|
||||
"mac/mac_ltvs.hpp",
|
||||
"mac/mac_types.cpp",
|
||||
"mac/mac_types.hpp",
|
||||
"mac/scan_result.cpp",
|
||||
|
||||
@@ -158,6 +158,7 @@ set(COMMON_SOURCES
|
||||
mac/mac_frame.cpp
|
||||
mac/mac_header_ie.cpp
|
||||
mac/mac_links.cpp
|
||||
mac/mac_ltvs.cpp
|
||||
mac/mac_types.cpp
|
||||
mac/scan_result.cpp
|
||||
mac/sub_mac.cpp
|
||||
|
||||
@@ -172,6 +172,14 @@ public:
|
||||
*/
|
||||
void SetLength(LengthType aLength) { mLength = aLength; }
|
||||
|
||||
/**
|
||||
* Indicates whether or not the `Data` is empty (length is zero).
|
||||
*
|
||||
* @retval TRUE The `Data` is empty (length is zero).
|
||||
* @retval FALSE The `Data` is not empty (length is non-zero).
|
||||
*/
|
||||
bool IsEmpty(void) const { return mLength == 0; }
|
||||
|
||||
/**
|
||||
* Copies the `Data` bytes to a given buffer.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file implements support for LTVs.
|
||||
*/
|
||||
|
||||
#include "mac_ltvs.hpp"
|
||||
|
||||
#include "common/bit_utils.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace Mac {
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
// Ltv::ParsedInfo
|
||||
|
||||
Error Ltv::ParsedInfo::ParseFromAndAdvance(FrameData &aFrameData)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
bool packed;
|
||||
uint8_t lenBitSize;
|
||||
uint8_t headerByte;
|
||||
|
||||
if (aFrameData.IsEmpty())
|
||||
{
|
||||
error = kErrorNotFound;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
IgnoreError(aFrameData.ReadUint8(headerByte));
|
||||
|
||||
if (aFrameData.IsEmpty())
|
||||
{
|
||||
// If no bytes remain after reading the header byte, it
|
||||
// corresponds to a zero-length packed LTV, where all 8 bits
|
||||
// of `headerByte` represent the Type. Note that no escape
|
||||
// mask check applies here, so even Type value 255 (all 1s)
|
||||
// is valid and packed.
|
||||
|
||||
mLength = 0;
|
||||
mType = headerByte;
|
||||
mValue = aFrameData.GetBytes();
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
packed = false;
|
||||
|
||||
lenBitSize = DetermineMinBitSizeFor(aFrameData.GetLength());
|
||||
|
||||
if (lenBitSize >= BitSizeOf(uint8_t))
|
||||
{
|
||||
mLength = headerByte;
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8_t lenMask = MaskForBitSize<uint8_t>(lenBitSize);
|
||||
uint8_t typeMask = ~lenMask;
|
||||
|
||||
mLength = (headerByte & lenMask);
|
||||
|
||||
// Check if the Type bits match the escape mask (all 1s in
|
||||
// upper bits), which indicates that the LTV uses base
|
||||
// (unpacked) format.
|
||||
|
||||
if ((headerByte & typeMask) != typeMask)
|
||||
{
|
||||
mType = (headerByte & typeMask) >> lenBitSize;
|
||||
packed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!packed)
|
||||
{
|
||||
SuccessOrExit(error = aFrameData.ReadUint8(mType));
|
||||
}
|
||||
|
||||
VerifyOrExit(aFrameData.CanRead(mLength), error = kErrorParse);
|
||||
|
||||
mValue = aFrameData.GetBytes();
|
||||
aFrameData.SkipOver(mLength);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
Error Ltv::ParsedInfo::FindIn(const FrameData &aFrameData, uint8_t aType)
|
||||
{
|
||||
FrameData frameData = aFrameData;
|
||||
|
||||
return FindInAndAdvance(frameData, aType);
|
||||
}
|
||||
|
||||
Error Ltv::ParsedInfo::FindInAndAdvance(FrameData &aFrameData, uint8_t aType)
|
||||
{
|
||||
Error error;
|
||||
|
||||
do
|
||||
{
|
||||
SuccessOrExit(error = ParseFromAndAdvance(aFrameData));
|
||||
} while (mType != aType);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
// Ltv::AppendInfo
|
||||
|
||||
void Ltv::AppendInfo::InitTypeLengthId(uint8_t aType, uint8_t aLength, uint16_t aId)
|
||||
{
|
||||
mType = aType;
|
||||
mLength = aLength;
|
||||
mValue = nullptr;
|
||||
mId = aId;
|
||||
}
|
||||
|
||||
bool Ltv::AppendInfo::CanUsePacked(uint8_t aLenBitSize) const
|
||||
{
|
||||
// Determines whether the LTV can be encoded in packed format given
|
||||
// the length field bit size `aLenBitSize`.
|
||||
|
||||
bool canPack = false;
|
||||
uint8_t remainingBitSize;
|
||||
|
||||
VerifyOrExit(aLenBitSize < BitSizeOf(uint8_t));
|
||||
|
||||
remainingBitSize = BitSizeOf(uint8_t) - aLenBitSize;
|
||||
|
||||
VerifyOrExit(DetermineMinBitSizeFor(mType) <= remainingBitSize);
|
||||
|
||||
// Type value of all `1` bits cannot be used in the packed format,
|
||||
// since that value is the "escape mask" that indicates base LTV
|
||||
// format is used.
|
||||
|
||||
VerifyOrExit(mType != MaskForBitSize<uint8_t>(remainingBitSize));
|
||||
|
||||
canPack = true;
|
||||
|
||||
exit:
|
||||
return canPack;
|
||||
}
|
||||
|
||||
void Ltv::AppendInfo::DetermineIfPackable(uint32_t &aTotalLength)
|
||||
{
|
||||
// Determines whether the LTV can be encoded in packed format
|
||||
// based on its Type value and the cumulative length at this point
|
||||
// (after this LTV). Also updates `aTotalLength` to include the
|
||||
// total encoded size of the LTV.
|
||||
|
||||
uint8_t lenBitSize;
|
||||
|
||||
aTotalLength += mLength;
|
||||
|
||||
if (aTotalLength == 0)
|
||||
{
|
||||
// If no bytes remain after reading the header byte, it
|
||||
// corresponds to the special zero-length packed LTV format
|
||||
// where 8 bits of `headerByte` represent the Type.
|
||||
// Importantly, the escape mask check does not apply, so even
|
||||
// Type value 255 (all 1s) is packed.
|
||||
|
||||
mIsPackable = true;
|
||||
mHeaderByte = mType;
|
||||
aTotalLength++;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
lenBitSize = DetermineMinBitSizeFor(aTotalLength);
|
||||
|
||||
if (CanUsePacked(lenBitSize))
|
||||
{
|
||||
mIsPackable = true;
|
||||
mHeaderByte = mLength | static_cast<uint8_t>(mType << lenBitSize);
|
||||
aTotalLength++;
|
||||
ExitNow();
|
||||
}
|
||||
|
||||
// Base format is used, which requires a separate 1-byte Type field.
|
||||
// We account for the extra byte in `aTotalLength` and re-evaluate
|
||||
// `lenBitSize` before using it to set the escape mask (all 1s in
|
||||
// upper bits).
|
||||
|
||||
aTotalLength++;
|
||||
lenBitSize = DetermineMinBitSizeFor(aTotalLength);
|
||||
|
||||
mIsPackable = false;
|
||||
mHeaderByte = mLength | ~MaskForBitSize<uint8_t>(lenBitSize);
|
||||
aTotalLength++;
|
||||
|
||||
exit:
|
||||
return;
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
// Ltv
|
||||
|
||||
Error Ltv::ValidateAllIn(const FrameData &aFrameData)
|
||||
{
|
||||
FrameData frameData = aFrameData;
|
||||
ParsedInfo ltvInfo;
|
||||
Error error;
|
||||
|
||||
do
|
||||
{
|
||||
error = ltvInfo.ParseFromAndAdvance(frameData);
|
||||
} while (error == kErrorNone);
|
||||
|
||||
return (error == kErrorNotFound) ? kErrorNone : error;
|
||||
}
|
||||
|
||||
Error Ltv::EncodeAndAppend(AppendInfo *aLtvList, uint16_t aNumLtvs, FrameBuilder &aBuilder)
|
||||
{
|
||||
Error error = kErrorNone;
|
||||
uint32_t totalLength = 0;
|
||||
|
||||
VerifyOrExit(aNumLtvs > 0);
|
||||
|
||||
// We first iterate backward from last to first LTV to evaluate
|
||||
// packability based on cumulative length. Then append all LTVs
|
||||
// in `aBuilder`.
|
||||
|
||||
for (uint16_t i = aNumLtvs; i > 0; i--)
|
||||
{
|
||||
aLtvList[i - 1].DetermineIfPackable(totalLength);
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < aNumLtvs; i++)
|
||||
{
|
||||
AppendInfo *ltv = &aLtvList[i];
|
||||
|
||||
SuccessOrExit(error = aBuilder.AppendUint8(ltv->mHeaderByte));
|
||||
|
||||
if (!ltv->mIsPackable)
|
||||
{
|
||||
SuccessOrExit(error = aBuilder.AppendUint8(ltv->mType));
|
||||
}
|
||||
|
||||
ltv->mValue = static_cast<uint8_t *>(aBuilder.AppendLength(ltv->mLength));
|
||||
VerifyOrExit(ltv->mValue != nullptr, error = kErrorNoBufs);
|
||||
}
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
} // namespace Mac
|
||||
} // namespace ot
|
||||
@@ -0,0 +1,379 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* This file includes definitions for generating and processing MAC LTVs.
|
||||
*/
|
||||
|
||||
#ifndef OT_CORE_MAC_MAC_LTVS_HPP_
|
||||
#define OT_CORE_MAC_MAC_LTVS_HPP_
|
||||
|
||||
#include "openthread-core-config.h"
|
||||
|
||||
#include "common/array.hpp"
|
||||
#include "common/bit_utils.hpp"
|
||||
#include "common/const_cast.hpp"
|
||||
#include "common/error.hpp"
|
||||
#include "common/frame_builder.hpp"
|
||||
#include "common/frame_data.hpp"
|
||||
|
||||
namespace ot {
|
||||
namespace Mac {
|
||||
|
||||
/**
|
||||
* @addtogroup core-mac
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements Thread Header IE Length-Type-Value (LTV) processing and encoding.
|
||||
*/
|
||||
class Ltv
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Represents LTV information.
|
||||
*/
|
||||
class Info
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Returns the LTV Type.
|
||||
*
|
||||
* @returns The LTV Type.
|
||||
*/
|
||||
uint8_t GetType(void) const { return mType; }
|
||||
|
||||
/**
|
||||
* Returns the LTV length in bytes.
|
||||
*
|
||||
* @returns The LTV length in bytes.
|
||||
*/
|
||||
uint8_t GetLength(void) const { return mLength; }
|
||||
|
||||
protected:
|
||||
Info(void) = default;
|
||||
|
||||
uint8_t mType;
|
||||
uint8_t mLength;
|
||||
const uint8_t *mValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents parsed LTV information.
|
||||
*
|
||||
* All parsing and search methods (`ParseFromAndAdvance()`, `FindIn()`, `FindInAndAdvance()`) automatically handle
|
||||
* packed and base (unpacked) LTV formats.
|
||||
*/
|
||||
class ParsedInfo : public Info
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Returns a pointer to the parsed LTV value bytes.
|
||||
*
|
||||
* @returns A pointer to the LTV value bytes.
|
||||
*/
|
||||
const void *GetValue(void) const { return mValue; }
|
||||
|
||||
/**
|
||||
* Returns a pointer to the parsed LTV value cast to a specified `ValueType`.
|
||||
*
|
||||
* @tparam ValueType The type to cast the value pointer to.
|
||||
*
|
||||
* @returns A pointer to the parsed LTV value cast to `const ValueType *`.
|
||||
*/
|
||||
template <typename ValueType> const ValueType *GetValueAs(void) const
|
||||
{
|
||||
return reinterpret_cast<const ValueType *>(GetValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the parsed LTV matches a specific `LtvType` structure and validates its value format.
|
||||
*
|
||||
* @tparam LtvType The LTV structure type to validate against. `LtvType` MUST define a `uint8_t kType`
|
||||
* constant and a static `bool IsValid(const ParsedInfo &aInfo)` method.
|
||||
*
|
||||
* @retval kErrorNone The LTV matches `LtvType::kType` and `LtvType::IsValid()` returns `true`.
|
||||
* @retval kErrorParse The LTV does not match `LtvType::kType` or `LtvType::IsValid()` returns `false`.
|
||||
*/
|
||||
template <typename LtvType> Error ValidateAs(void) const
|
||||
{
|
||||
return ((GetType() == LtvType::kType) && LtvType::IsValid(*this)) ? kErrorNone : kErrorParse;
|
||||
}
|
||||
|
||||
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// Parsing or Finding LTVs in a `FrameData`.
|
||||
|
||||
/**
|
||||
* Parses an LTV element from a given `FrameData` and updates `aFrameData` to skip over it.
|
||||
*
|
||||
* @param[in,out] aFrameData The frame data buffer to parse from.
|
||||
*
|
||||
* @retval kErrorNone Successfully parsed the LTV element and advanced @p aFrameData.
|
||||
* @retval kErrorNotFound No LTV element remains in @p aFrameData.
|
||||
* @retval kErrorParse The LTV element is malformed.
|
||||
*/
|
||||
Error ParseFromAndAdvance(FrameData &aFrameData);
|
||||
|
||||
/**
|
||||
* Searches a `FrameData` for an LTV element matching a given Type.
|
||||
*
|
||||
* @param[in] aFrameData The frame data buffer to search within.
|
||||
* @param[in] aType The LTV Type to search for.
|
||||
*
|
||||
* @retval kErrorNone Successfully found an LTV element matching @p aType.
|
||||
* @retval kErrorNotFound No LTV element matching @p aType was found in @p aFrameData.
|
||||
* @retval kErrorParse An LTV element in @p aFrameData is malformed.
|
||||
*/
|
||||
Error FindIn(const FrameData &aFrameData, uint8_t aType);
|
||||
|
||||
/**
|
||||
* Searches a `FrameData` for an LTV element matching a given Type and advances `aFrameData` past it.
|
||||
*
|
||||
* @param[in,out] aFrameData The frame data buffer to search within and advance.
|
||||
* @param[in] aType The LTV Type to search for.
|
||||
*
|
||||
* @retval kErrorNone Successfully found an LTV element matching @p aType and advanced @p aFrameData.
|
||||
* @retval kErrorNotFound No LTV element matching @p aType was found in @p aFrameData.
|
||||
* @retval kErrorParse An LTV element in @p aFrameData is malformed.
|
||||
*/
|
||||
Error FindInAndAdvance(FrameData &aFrameData, uint8_t aType);
|
||||
|
||||
/**
|
||||
* Searches a `FrameData` for an LTV element of a specific `LtvType` and validates it.
|
||||
*
|
||||
* @tparam LtvType The LTV structure type to search for and validate against. `LtvType` MUST define a
|
||||
* `uint8_t kType` constant and a static `bool IsValid(const ParsedInfo &aInfo)` method.
|
||||
*
|
||||
* @param[in] aFrameData The frame data buffer to search within.
|
||||
*
|
||||
* @retval kErrorNone Successfully found an LTV element matching `LtvType::kType` and validated it.
|
||||
* @retval kErrorNotFound No LTV element matching `LtvType::kType` was found in @p aFrameData.
|
||||
* @retval kErrorParse An LTV element in @p aFrameData is malformed or invalid per `LtvType::IsValid()`.
|
||||
*/
|
||||
template <typename LtvType> Error FindAndValidate(const FrameData &aFrameData)
|
||||
{
|
||||
Error error;
|
||||
|
||||
SuccessOrExit(error = FindIn(aFrameData, LtvType::kType));
|
||||
VerifyOrExit(LtvType::IsValid(*this), error = kErrorParse);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches a `FrameData` for an LTV element of a specific `LtvType`, validates it, and advances `aFrameData`
|
||||
* past it.
|
||||
*
|
||||
* @tparam LtvType The LTV structure type to search for and validate against. `LtvType` MUST define a
|
||||
* `uint8_t kType` constant and a static `bool IsValid(const ParsedInfo &aInfo)`
|
||||
* method.
|
||||
*
|
||||
* @param[in,out] aFrameData The frame data buffer to search within and advance.
|
||||
*
|
||||
* @retval kErrorNone Successfully found an LTV element matching `LtvType::kType`, validated it, and
|
||||
* advanced @p aFrameData.
|
||||
* @retval kErrorNotFound No LTV element matching `LtvType::kType` was found in @p aFrameData.
|
||||
* @retval kErrorParse An LTV element in @p aFrameData is malformed or invalid per `LtvType::IsValid()`.
|
||||
*/
|
||||
template <typename LtvType> Error FindValidateAndAdvance(FrameData &aFrameData)
|
||||
{
|
||||
Error error;
|
||||
|
||||
SuccessOrExit(error = FindInAndAdvance(aFrameData, LtvType::kType));
|
||||
VerifyOrExit(LtvType::IsValid(*this), error = kErrorParse);
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents information used to construct and append an LTV element.
|
||||
*
|
||||
* To encode and append one or more LTVs into a `FrameBuilder`:
|
||||
*
|
||||
* 1. Prepare an array of `AppendInfo` instances (one per LTV) and initialize each using the `Init...()` methods.
|
||||
* 2. Call `EncodeAndAppend()` passing the array of `AppendInfo` entries.
|
||||
* 3. `EncodeAndAppend()` determines whether each LTV can be encoded using the packed or base format.
|
||||
* It appends the LTV headers to the `FrameBuilder` and allocates space for their value bytes.
|
||||
* 4. After a successful call to `EncodeAndAppend()`, the caller can use `GetValue()` or `GetValueAs()` on each
|
||||
* LTV entry to populate its value bytes in the frame.
|
||||
*/
|
||||
class AppendInfo : public Info
|
||||
{
|
||||
friend class Ltv;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initializes an LTV `AppendInfo` instance with a Type and Length.
|
||||
*
|
||||
* @param[in] aType The LTV Type.
|
||||
* @param[in] aLength The LTV length in bytes.
|
||||
*/
|
||||
void InitTypeLength(uint8_t aType, uint8_t aLength) { InitTypeLengthId(aType, aLength, 0); }
|
||||
|
||||
/**
|
||||
* Initializes an `AppendInfo` instance with a Type, Length, and an identifier.
|
||||
*
|
||||
* The @p aId can be used to distinguish between multiple LTV instances of the same Type in an array.
|
||||
*
|
||||
* @param[in] aType The LTV Type.
|
||||
* @param[in] aLength The LTV length in bytes.
|
||||
* @param[in] aId A caller-specified identifier.
|
||||
*/
|
||||
void InitTypeLengthId(uint8_t aType, uint8_t aLength, uint16_t aId);
|
||||
|
||||
/**
|
||||
* Initializes an `AppendInfo` instance for a specific `LtvType` with a given length and optional identifier.
|
||||
*
|
||||
* @tparam LtvType The LTV structure type to initialize for. `LtvType` MUST define a `uint8_t kType` constant.
|
||||
*
|
||||
* @param[in] aLength The LTV length in bytes.
|
||||
* @param[in] aId An optional caller-specified identifier.
|
||||
*/
|
||||
template <typename LtvType> void InitAsWithLength(uint8_t aLength, uint16_t aId = 0)
|
||||
{
|
||||
InitTypeLengthId(LtvType::kType, aLength, aId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes an `AppendInfo` instance for a specific fixed-length `LtvType` with an optional identifier.
|
||||
*
|
||||
* @tparam LtvType The LTV structure type to initialize for. `LtvType` MUST define `uint8_t kType` and
|
||||
* `uint8_t kLength` constants.
|
||||
*
|
||||
* @param[in] aId An optional caller-specified identifier.
|
||||
*/
|
||||
template <typename LtvType> void InitAs(uint16_t aId = 0)
|
||||
{
|
||||
InitTypeLengthId(LtvType::kType, LtvType::kLength, aId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the optional identifier assigned to this LTV instance.
|
||||
*
|
||||
* @returns The caller-specified identifier.
|
||||
*/
|
||||
uint16_t GetId(void) const { return mId; }
|
||||
|
||||
/**
|
||||
* Returns a pointer to the appended LTV value buffer.
|
||||
*
|
||||
* The caller can write the LTV value directly into this buffer after a successful call to `EncodeAndAppend()`.
|
||||
*
|
||||
* @returns A pointer to the LTV value buffer.
|
||||
*/
|
||||
void *GetValue(void) { return AsNonConst(mValue); }
|
||||
|
||||
/**
|
||||
* Returns a pointer to the appended LTV value buffer cast to a specified `ValueType`.
|
||||
*
|
||||
* The caller can update the LTV value using the returned pointer after a successful call to
|
||||
* `EncodeAndAppend()`.
|
||||
*
|
||||
* @tparam ValueType The type to cast the value pointer to.
|
||||
*
|
||||
* @returns A pointer to the LTV value buffer cast to `ValueType *`.
|
||||
*/
|
||||
template <typename ValueType> ValueType *GetValueAs(void) { return reinterpret_cast<ValueType *>(GetValue()); }
|
||||
|
||||
private:
|
||||
bool CanUsePacked(uint8_t aLenBitSize) const;
|
||||
void DetermineIfPackable(uint32_t &aTotalLength);
|
||||
|
||||
uint16_t mId;
|
||||
uint8_t mHeaderByte;
|
||||
bool mIsPackable;
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates all LTV elements contained in a `FrameData`.
|
||||
*
|
||||
* @param[in] aFrameData The frame data buffer containing LTV elements.
|
||||
*
|
||||
* @retval kErrorNone All LTV elements in @p aFrameData were successfully parsed and validated.
|
||||
* @retval kErrorParse An LTV element in @p aFrameData is malformed.
|
||||
*/
|
||||
static Error ValidateAllIn(const FrameData &aFrameData);
|
||||
|
||||
/**
|
||||
* Encodes and appends a list of LTV elements into a `FrameBuilder`.
|
||||
*
|
||||
* This method determines whether each LTV can use the packed or base format based on its Type and cumulative
|
||||
* remaining length. It appends the LTV headers to @p aBuilder and reserves space for their value bytes.
|
||||
*
|
||||
* After this method successfully returns, the LTV value bytes in @p aBuilder are allocated but left uninitialized.
|
||||
* The caller can populate the value bytes using `GetValue()` or `GetValueAs()` on each entry in @p aLtvList.
|
||||
*
|
||||
* @param[in,out] aLtvList An array of `AppendInfo` entries representing the LTVs to append.
|
||||
* @param[in] aNumLtvs The number of LTV elements in @p aLtvList.
|
||||
* @param[in,out] aBuilder The `FrameBuilder` instance to append to.
|
||||
*
|
||||
* @retval kErrorNone Successfully encoded and appended all LTVs.
|
||||
* @retval kErrorNoBufs Insufficient space in @p aBuilder to append the LTV elements.
|
||||
*/
|
||||
static Error EncodeAndAppend(AppendInfo *aLtvList, uint16_t aNumLtvs, FrameBuilder &aBuilder);
|
||||
|
||||
private:
|
||||
Ltv(void) = delete;
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Defines Target ID LTV constants and types and helper methods.
|
||||
*/
|
||||
struct TargetIdLtv
|
||||
{
|
||||
static constexpr uint8_t kType = 0x01; ///< Target ID LTV Type.
|
||||
|
||||
static constexpr uint8_t kMinLength = 1; ///< Minimum Target ID LTV length in bytes.
|
||||
|
||||
/**
|
||||
* Validates whether a parsed LTV is a valid Target ID LTV.
|
||||
*
|
||||
* @param[in] aLtv The parsed LTV to validate.
|
||||
*
|
||||
* @retval TRUE @p aLtv is a valid Target ID LTV.
|
||||
* @retval FALSE @p aLtv is not a valid Target ID LTV.
|
||||
*/
|
||||
static bool IsValid(const Ltv::Info &aLtv) { return aLtv.GetLength() >= kMinLength; }
|
||||
};
|
||||
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
} // namespace Mac
|
||||
} // namespace ot
|
||||
|
||||
#endif // OT_CORE_MAC_MAC_LTVS_HPP_
|
||||
@@ -233,6 +233,7 @@ ot_unit_test(link_metrics_manager)
|
||||
ot_unit_test(link_quality)
|
||||
ot_unit_test(linked_list)
|
||||
ot_unit_test(lowpan)
|
||||
ot_unit_test(ltv)
|
||||
ot_unit_test(mac_frame)
|
||||
ot_unit_test(macros)
|
||||
ot_unit_test(mdns)
|
||||
|
||||
@@ -0,0 +1,735 @@
|
||||
/*
|
||||
* 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");
|
||||
}
|
||||
|
||||
} // 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();
|
||||
|
||||
printf("\nAll tests passed\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user