[mac] add dynamic HeaderIe building with StartIe() and EndIe() (#13291)

This commit introduces dynamic Header IE generation in `HeaderIe`
using `StartIe()`, `EndIe()`, and `Bookmark`. It allows appending
variable-length Header IEs directly into a `FrameBuilder` without
pre-computing the IE length.

It also adds unit test case `TestMacHeaderIeStartEnd()` in
`test_mac_frame.cpp` covering normal usage, empty IEs, max length
(127 bytes), and error cases for the new methods.
This commit is contained in:
Abtin Keshavarzian
2026-07-03 21:03:22 -07:00
committed by GitHub
parent ef9c8de3cd
commit a4bf4f9899
4 changed files with 176 additions and 0 deletions
+19
View File
@@ -297,6 +297,25 @@ public:
*/
void RemoveBytes(uint16_t aOffset, uint16_t aLength);
/**
* Reads a pointer to a previously appended object in the `FrameBuilder` at a given byte offset.
*
* This method does not perform any bounds checking. The caller MUST ensure the object of type `ObjectType`
* fits within the previously appended content.
*
* @tparam ObjectType The object type to read.
*
* @param[in] aOffset The byte offset where the object starts.
*
* @returns A pointer to the `ObjectType` at @p aOffset.
*/
template <typename ObjectType> ObjectType *Read(uint16_t aOffset)
{
static_assert(!TypeTraits::IsPointer<ObjectType>::kValue, "ObjectType must not be a pointer");
return reinterpret_cast<ObjectType *>(mBuffer + aOffset);
}
private:
uint8_t *mBuffer;
uint16_t mLength;
+36
View File
@@ -43,6 +43,42 @@ void HeaderIe::Init(uint8_t aId, uint8_t aLen)
SetId(aId);
}
Error HeaderIe::StartIe(FrameBuilder &aBuilder, uint8_t aId, Bookmark &aBookmark)
{
Error error = kErrorNone;
HeaderIe *ie;
aBookmark = aBuilder.GetLength();
ie = aBuilder.Append<HeaderIe>();
VerifyOrExit(ie != nullptr, error = kErrorNoBufs);
ie->Init(aId, 0);
exit:
return error;
}
Error HeaderIe::EndIe(FrameBuilder &aBuilder, const Bookmark &aBookmark)
{
Error error = kErrorNone;
uint16_t offset = aBookmark;
uint16_t length;
HeaderIe *ie;
VerifyOrExit(offset + sizeof(HeaderIe) <= aBuilder.GetLength(), error = kErrorInvalidArgs);
ie = aBuilder.Read<HeaderIe>(offset);
length = aBuilder.GetLength() - offset - sizeof(HeaderIe);
VerifyOrExit(length <= kMaxLength, error = kErrorInvalidArgs);
ie->SetLength(static_cast<uint8_t>(length));
exit:
return error;
}
#if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE
Error ConnectionIe::SetWakeupId(WakeupId aWakeupId)
+38
View File
@@ -40,6 +40,7 @@
#include "common/bit_utils.hpp"
#include "common/const_cast.hpp"
#include "common/encoding.hpp"
#include "common/frame_builder.hpp"
#include "common/num_utils.hpp"
#include "common/numeric_limits.hpp"
#include "mac/mac_types.hpp"
@@ -60,6 +61,8 @@ OT_TOOL_PACKED_BEGIN
class HeaderIe
{
public:
static constexpr uint8_t kMaxLength = 127; ///< Maximum Header IE length in bytes.
/**
* Returns the IE Element ID.
*
@@ -115,6 +118,41 @@ public:
return (aIe.GetId() == IeType::kId) && static_cast<const IeType *>(&aIe)->IsValid();
}
/**
* Represents the opaque type for a bookmark used by `StartIe()/EndIe()`.
*/
typedef uint16_t Bookmark;
/**
* Starts appending a (variable-length) `HeaderIe` in a `FrameBuilder`.
*
* On success, this method appends a `HeaderIe` descriptor (with length initialized to zero) to @p aBuilder and
* saves the current byte offset as @p aBookmark. The caller can then append the IE content bytes to @p aBuilder,
* and finally call `EndIe()` to update the IE length field automatically.
*
* @param[in,out] aBuilder The `FrameBuilder` instance to append to.
* @param[in] aId The IE Element ID.
* @param[out] aBookmark A reference to a `Bookmark` to save the start offset.
*
* @retval kErrorNone Successfully started the `HeaderIe`.
* @retval kErrorNoBufs Insufficient space in @p aBuilder to append the `HeaderIe` header.
*/
static Error StartIe(FrameBuilder &aBuilder, uint8_t aId, Bookmark &aBookmark);
/**
* Finishes appending a `HeaderIe` in a `FrameBuilder`.
*
* This method updates the length field of the `HeaderIe` previously started using `StartIe()`. It determines the
* IE length based on the number of bytes appended to @p aBuilder since `StartIe()` was called.
*
* @param[in,out] aBuilder The `FrameBuilder` instance.
* @param[in] aBookmark The `Bookmark` used when calling `StartIe()`.
*
* @retval kErrorNone Successfully finalized the `HeaderIe` length.
* @retval kErrorInvalidArgs The @p aBookmark is invalid or the appended IE length exceeds `kMaxLength`.
*/
static Error EndIe(FrameBuilder &aBuilder, const Bookmark &aBookmark);
protected:
void Init(uint8_t aId, uint8_t aLen);
uint8_t *GetBytes(void) { return reinterpret_cast<uint8_t *>(this); }
+83
View File
@@ -470,6 +470,88 @@ void VerifyChannelMaskContent(const Mac::ChannelMask &aMask, uint8_t *aChannels,
VerifyOrQuit(aLength == aMask.GetNumberOfChannels());
}
void TestMacHeaderIeStartEnd(void)
{
using HeaderIe = Mac::HeaderIe;
static const uint8_t kIeId1 = 0x0a;
static const uint8_t kIeId2 = 0x0b;
static const uint8_t kIeId3 = 0x0c;
static const uint8_t kContent[] = {0x11, 0x22, 0x33, 0x44, 0x55};
uint8_t buffer[256];
FrameBuilder builder;
uint16_t offset;
HeaderIe::Bookmark bookmark;
HeaderIe *ie;
uint8_t largeContent[128];
printf("TestMacHeaderIeStartEnd\n");
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Normal use: Append an IE (5 bytes content)
builder.Init(buffer, sizeof(buffer));
SuccessOrQuit(HeaderIe::StartIe(builder, kIeId1, bookmark));
VerifyOrQuit(builder.GetLength() == sizeof(HeaderIe));
SuccessOrQuit(builder.AppendBytes(kContent, sizeof(kContent)));
SuccessOrQuit(HeaderIe::EndIe(builder, bookmark));
VerifyOrQuit(builder.GetLength() == sizeof(HeaderIe) + sizeof(kContent));
ie = reinterpret_cast<HeaderIe *>(buffer);
VerifyOrQuit(ie->GetId() == kIeId1);
VerifyOrQuit(ie->GetLength() == sizeof(kContent));
VerifyOrQuit(ie->GetSize() == sizeof(HeaderIe) + sizeof(kContent));
VerifyOrQuit(memcmp(ie->GetContent(), kContent, sizeof(kContent)) == 0);
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Normal use: Append a new IE with empty payload (0-length IE)
offset = builder.GetLength();
SuccessOrQuit(HeaderIe::StartIe(builder, kIeId2, bookmark));
SuccessOrQuit(HeaderIe::EndIe(builder, bookmark));
// First IE should remain untouched
ie = reinterpret_cast<HeaderIe *>(buffer);
VerifyOrQuit(ie->GetId() == kIeId1);
VerifyOrQuit(ie->GetLength() == sizeof(kContent));
VerifyOrQuit(ie->GetSize() == sizeof(HeaderIe) + sizeof(kContent));
VerifyOrQuit(memcmp(ie->GetContent(), kContent, sizeof(kContent)) == 0);
// Second IE with empty content
ie = builder.Read<HeaderIe>(offset);
VerifyOrQuit(ie->GetId() == kIeId2);
VerifyOrQuit(ie->GetLength() == 0);
VerifyOrQuit(ie->GetSize() == sizeof(HeaderIe));
VerifyOrQuit(builder.GetLength() == 2 * sizeof(HeaderIe) + sizeof(kContent));
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Maximum allowed IE length (127 bytes)
memset(largeContent, 0xaa, sizeof(largeContent));
builder.Init(buffer, sizeof(buffer));
SuccessOrQuit(HeaderIe::StartIe(builder, kIeId3, bookmark));
SuccessOrQuit(builder.AppendBytes(largeContent, HeaderIe::kMaxLength));
SuccessOrQuit(HeaderIe::EndIe(builder, bookmark));
ie = builder.Read<HeaderIe>(0);
VerifyOrQuit(ie->GetId() == kIeId3);
VerifyOrQuit(ie->GetLength() == HeaderIe::kMaxLength);
VerifyOrQuit(builder.GetLength() == ie->GetSize());
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Exceeding maximum length (128 bytes > 127)
builder.Init(buffer, sizeof(buffer));
SuccessOrQuit(HeaderIe::StartIe(builder, kIeId3, bookmark));
SuccessOrQuit(builder.AppendBytes(largeContent, HeaderIe::kMaxLength + 1));
VerifyOrQuit(HeaderIe::EndIe(builder, bookmark) == kErrorInvalidArgs);
}
void TestMacChannelMask(void)
{
uint8_t allChannels[] = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26};
@@ -788,6 +870,7 @@ int main(void)
{
ot::TestMacAddress();
ot::TestMacHeader();
ot::TestMacHeaderIeStartEnd();
ot::TestMacChannelMask();
ot::TestMacFrameApi();
ot::TestMacFrameAckGeneration();