diff --git a/src/core/common/frame_builder.hpp b/src/core/common/frame_builder.hpp index 09cfbe9fe..528b6feef 100644 --- a/src/core/common/frame_builder.hpp +++ b/src/core/common/frame_builder.hpp @@ -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 ObjectType *Read(uint16_t aOffset) + { + static_assert(!TypeTraits::IsPointer::kValue, "ObjectType must not be a pointer"); + + return reinterpret_cast(mBuffer + aOffset); + } + private: uint8_t *mBuffer; uint16_t mLength; diff --git a/src/core/mac/mac_header_ie.cpp b/src/core/mac/mac_header_ie.cpp index 26959edc3..c9e5bf2a8 100644 --- a/src/core/mac/mac_header_ie.cpp +++ b/src/core/mac/mac_header_ie.cpp @@ -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(); + 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(offset); + + length = aBuilder.GetLength() - offset - sizeof(HeaderIe); + VerifyOrExit(length <= kMaxLength, error = kErrorInvalidArgs); + + ie->SetLength(static_cast(length)); + +exit: + return error; +} + #if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE Error ConnectionIe::SetWakeupId(WakeupId aWakeupId) diff --git a/src/core/mac/mac_header_ie.hpp b/src/core/mac/mac_header_ie.hpp index 21c843ab3..9da8b243f 100644 --- a/src/core/mac/mac_header_ie.hpp +++ b/src/core/mac/mac_header_ie.hpp @@ -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(&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(this); } diff --git a/tests/unit/test_mac_frame.cpp b/tests/unit/test_mac_frame.cpp index c8987fb10..575624624 100644 --- a/tests/unit/test_mac_frame.cpp +++ b/tests/unit/test_mac_frame.cpp @@ -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(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(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(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(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();