[mesh-forwarder] introduce MessageFramer to consolidate frame prep logic (#11817)

This change introduces a new `MessageFramer` class to encapsulate the
logic for preparing MAC data frames.

This change improves code modularity and separation of concerns by
isolating the frame preparation logic (MAC headers, mesh headers,
6LoWPAN compression, and fragmentation) from the message forwarding
responsibilities of `MeshForwarder`.

Classes such as `MeshForwarder`, `IndirectSender`, and
`DataPollSender` are updated to use the new `MessageFramer` class.
This commit is contained in:
Abtin Keshavarzian
2025-08-18 10:41:01 -07:00
committed by GitHub
parent ca372b8e99
commit 9911440505
12 changed files with 558 additions and 422 deletions
+2
View File
@@ -665,6 +665,8 @@ openthread_core_files = [
"thread/mesh_forwarder.hpp",
"thread/mesh_forwarder_ftd.cpp",
"thread/mesh_forwarder_mtd.cpp",
"thread/message_framer.cpp",
"thread/message_framer.hpp",
"thread/mle.cpp",
"thread/mle.hpp",
"thread/mle_ftd.cpp",
+1
View File
@@ -227,6 +227,7 @@ set(COMMON_SOURCES
thread/mesh_forwarder.cpp
thread/mesh_forwarder_ftd.cpp
thread/mesh_forwarder_mtd.cpp
thread/message_framer.cpp
thread/mle.cpp
thread/mle_ftd.cpp
thread/mle_tlvs.cpp
+1
View File
@@ -145,6 +145,7 @@ Instance::Instance(void)
, mKeyManager(*this)
, mLowpan(*this)
, mMac(*this)
, mMessageFramer(*this)
, mMeshForwarder(*this)
, mMle(*this)
, mDiscoverScanner(*this)
+4
View File
@@ -122,6 +122,7 @@
#include "thread/link_metrics.hpp"
#include "thread/link_quality.hpp"
#include "thread/mesh_forwarder.hpp"
#include "thread/message_framer.hpp"
#include "thread/mle.hpp"
#include "thread/mlr_manager.hpp"
#include "thread/network_data_local.hpp"
@@ -549,6 +550,7 @@ private:
KeyManager mKeyManager;
Lowpan::Lowpan mLowpan;
Mac::Mac mMac;
MessageFramer mMessageFramer;
MeshForwarder mMeshForwarder;
Mle::Mle mMle;
Mle::DiscoverScanner mDiscoverScanner;
@@ -762,6 +764,8 @@ template <> inline Settings &Instance::Get(void) { return mSettings; }
template <> inline SettingsDriver &Instance::Get(void) { return mSettingsDriver; }
template <> inline MessageFramer &Instance::Get(void) { return mMessageFramer; }
template <> inline MeshForwarder &Instance::Get(void) { return mMeshForwarder; }
#if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE
+1 -1
View File
@@ -571,7 +571,7 @@ Mac::TxFrame *DataPollSender::PrepareDataRequest(Mac::TxFrames &aTxFrames)
frameInfo.mSecurityLevel = Mac::Frame::kSecurityEncMic32;
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode1;
Get<MeshForwarder>().PrepareMacHeaders(*frame, frameInfo, nullptr);
Get<MessageFramer>().PrepareMacHeaders(*frame, frameInfo, nullptr);
#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
if (frame->HasCslIe())
+27 -53
View File
@@ -325,86 +325,60 @@ void IndirectSender::UpdateIndirectMessage(Child &aChild)
Error IndirectSender::PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild)
{
Error error = kErrorNone;
Message *message = aChild.GetIndirectMessage();
VerifyOrExit(mEnabled, error = kErrorAbort);
if (message == nullptr)
{
PrepareEmptyFrame(aFrame, aChild, /* aAckRequest */ true);
aContext.mMessageNextOffset = 0;
ExitNow();
}
switch (message->GetType())
{
case Message::kTypeIp6:
aContext.mMessageNextOffset = PrepareDataFrame(aFrame, aChild, *message);
break;
case Message::kTypeSupervision:
PrepareEmptyFrame(aFrame, aChild, /* aAckRequest */ true);
aContext.mMessageNextOffset = message->GetLength();
break;
default:
OT_ASSERT(false);
}
exit:
return error;
}
uint16_t IndirectSender::PrepareDataFrame(Mac::TxFrame &aFrame, Child &aChild, Message &aMessage)
{
Error error = kErrorNone;
Message *message;
Ip6::Header ip6Header;
Mac::Addresses macAddrs;
uint16_t directTxOffset;
uint16_t nextOffset;
VerifyOrExit(mEnabled, error = kErrorAbort);
aChild.GetMacAddress(macAddrs.mDestination);
message = aChild.GetIndirectMessage();
if ((message == nullptr) || (message->GetType() == Message::kTypeSupervision))
{
Get<MessageFramer>().PrepareEmptyFrame(aFrame, macAddrs.mDestination, /* aAckRequest */ true);
aContext.mMessageNextOffset = (message == nullptr) ? 0 : message->GetLength();
ExitNow();
}
VerifyOrExit(message->GetType() == Message::kTypeIp6);
// Determine the MAC source and destination addresses.
IgnoreError(aMessage.Read(0, ip6Header));
IgnoreError(message->Read(0, ip6Header));
Get<MeshForwarder>().GetMacSourceAddress(ip6Header.GetSource(), macAddrs.mSource);
Get<MessageFramer>().DetermineMacSourceAddress(ip6Header.GetSource(), macAddrs);
if (ip6Header.GetDestination().IsLinkLocalUnicast())
{
macAddrs.mDestination.SetExtendedFromIid(ip6Header.GetDestination().GetIid());
}
else
{
aChild.GetMacAddress(macAddrs.mDestination);
}
// Prepare the data frame from previous child's indirect offset.
directTxOffset = aMessage.GetOffset();
aMessage.SetOffset(aChild.GetIndirectFragmentOffset());
directTxOffset = message->GetOffset();
message->SetOffset(aChild.GetIndirectFragmentOffset());
nextOffset = Get<MeshForwarder>().PrepareDataFrameWithNoMeshHeader(aFrame, aMessage, macAddrs);
aContext.mMessageNextOffset = Get<MessageFramer>().PrepareFrame(aFrame, *message, macAddrs);
aMessage.SetOffset(directTxOffset);
message->SetOffset(directTxOffset);
// Set `FramePending` if there are more queued messages (excluding
// the current one being sent out) for the child (note `> 1` check).
// The case where the current message itself requires fragmentation
// is already checked and handled in `PrepareDataFrame()` method.
// is already checked and handled in the above `PrepareFrame` call.
if (aChild.GetIndirectMessageCount() > 1)
{
aFrame.SetFramePending(true);
}
return nextOffset;
}
void IndirectSender::PrepareEmptyFrame(Mac::TxFrame &aFrame, Child &aChild, bool aAckRequest)
{
Mac::Address macDest;
aChild.GetMacAddress(macDest);
Get<MeshForwarder>().PrepareEmptyFrame(aFrame, macDest, aAckRequest);
exit:
return error;
}
void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame,
+3 -5
View File
@@ -273,11 +273,9 @@ private:
void HandleSentFrameToChild(const Mac::TxFrame &aFrame, const FrameContext &aContext, Error aError, Child &aChild);
void HandleFrameChangeDone(Child &aChild);
void UpdateIndirectMessage(Child &aChild);
void RequestMessageUpdate(Child &aChild);
uint16_t PrepareDataFrame(Mac::TxFrame &aFrame, Child &aChild, Message &aMessage);
void PrepareEmptyFrame(Mac::TxFrame &aFrame, Child &aChild, bool aAckRequest);
void ClearMessagesForRemovedChildren(void);
void UpdateIndirectMessage(Child &aChild);
void RequestMessageUpdate(Child &aChild);
void ClearMessagesForRemovedChildren(void);
static bool AcceptAnyMessage(const Message &aMessage);
static bool AcceptSupervisionMessage(const Message &aMessage);
+5 -329
View File
@@ -84,8 +84,6 @@ MeshForwarder::MeshForwarder(Instance &aInstance)
#endif
, mDataPollSender(aInstance)
{
mFragTag = Random::NonCrypto::GetUint16();
#if OPENTHREAD_CONFIG_TX_QUEUE_STATISTICS_ENABLE
mTxQueueStats.Clear();
#endif
@@ -133,30 +131,6 @@ exit:
return;
}
void MeshForwarder::PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address &aMacDest, bool aAckRequest)
{
Mac::TxFrame::Info frameInfo;
frameInfo.mAddrs.mSource.SetShort(Get<Mac::Mac>().GetShortAddress());
if (frameInfo.mAddrs.mSource.IsShortAddrInvalid() || aMacDest.IsExtended())
{
frameInfo.mAddrs.mSource.SetExtended(Get<Mac::Mac>().GetExtAddress());
}
frameInfo.mAddrs.mDestination = aMacDest;
frameInfo.mPanIds.SetBothSourceDestination(Get<Mac::Mac>().GetPanId());
frameInfo.mType = Mac::Frame::kTypeData;
frameInfo.mSecurityLevel = Mac::Frame::kSecurityEncMic32;
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode1;
PrepareMacHeaders(aFrame, frameInfo, nullptr);
aFrame.SetAckRequest(aAckRequest);
aFrame.SetPayloadLength(0);
}
void MeshForwarder::ResumeMessageTransmissions(void)
{
if (mTxPaused)
@@ -600,7 +574,7 @@ Error MeshForwarder::UpdateIp6Route(Message &aMessage)
VerifyOrExit(!ip6Header.GetSource().IsMulticast(), error = kErrorDrop);
GetMacSourceAddress(ip6Header.GetSource(), mMacAddrs.mSource);
Get<MessageFramer>().DetermineMacSourceAddress(ip6Header.GetSource(), mMacAddrs);
if (mle.IsDisabled() || mle.IsDetached())
{
@@ -674,16 +648,6 @@ void MeshForwarder::SetRxOnWhenIdle(bool aRxOnWhenIdle)
}
}
void MeshForwarder::GetMacSourceAddress(const Ip6::Address &aIp6Addr, Mac::Address &aMacAddr)
{
aMacAddr.SetExtendedFromIid(aIp6Addr.GetIid());
if (aMacAddr.GetExtended() != Get<Mac::Mac>().GetExtAddress())
{
aMacAddr.SetShort(Get<Mac::Mac>().GetShortAddress());
}
}
Mac::TxFrame *MeshForwarder::HandleFrameRequest(Mac::TxFrames &aTxFrames)
{
Mac::TxFrame *frame = nullptr;
@@ -724,8 +688,8 @@ Mac::TxFrame *MeshForwarder::HandleFrameRequest(Mac::TxFrames &aTxFrames)
mSendMessage->SetLinkSecurityEnabled(true);
}
#endif
mMessageNextOffset =
PrepareDataFrame(*frame, *mSendMessage, mMacAddrs, mAddMeshHeader, mMeshSource, mMeshDest, addFragHeader);
mMessageNextOffset = Get<MessageFramer>().PrepareFrame(*frame, *mSendMessage, mMacAddrs, mAddMeshHeader,
mMeshSource, mMeshDest, addFragHeader);
if (mSendMessage->IsMleCommand(Mle::kCommandChildIdRequest) && mSendMessage->IsLinkSecurityEnabled())
{
@@ -741,7 +705,7 @@ Mac::TxFrame *MeshForwarder::HandleFrameRequest(Mac::TxFrames &aTxFrames)
Mac::Address macDestAddr;
macDestAddr.SetShort(Get<Mle::Mle>().GetParent().GetRloc16());
PrepareEmptyFrame(*frame, macDestAddr, /* aAckRequest */ true);
Get<MessageFramer>().PrepareEmptyFrame(*frame, macDestAddr, /* aAckRequest */ true);
}
break;
#endif
@@ -749,7 +713,7 @@ Mac::TxFrame *MeshForwarder::HandleFrameRequest(Mac::TxFrames &aTxFrames)
#if OPENTHREAD_FTD
case Message::kType6lowpan:
SendMesh(*mSendMessage, *frame);
mMessageNextOffset = Get<MessageFramer>().PrepareMeshFrame(*frame, *mSendMessage, mMacAddrs);
break;
case Message::kTypeSupervision:
@@ -773,294 +737,6 @@ exit:
return frame;
}
void MeshForwarder::PrepareMacHeaders(Mac::TxFrame &aTxFrame, Mac::TxFrame::Info &aTxFrameInfo, const Message *aMessage)
{
const Neighbor *neighbor;
aTxFrameInfo.mVersion = Mac::Frame::kVersion2006;
#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
#if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE) || OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || \
OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Determine frame version and Header IE entries
neighbor = Get<NeighborTable>().FindNeighbor(aTxFrameInfo.mAddrs.mDestination);
if (neighbor == nullptr)
{
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
else if (Get<Mac::Mac>().IsCslEnabled())
{
aTxFrameInfo.mAppendCslIe = true;
aTxFrameInfo.mVersion = Mac::Frame::kVersion2015;
}
#endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
else if ((Get<ChildTable>().Contains(*neighbor) && static_cast<const Child *>(neighbor)->IsCslSynchronized()))
{
aTxFrameInfo.mVersion = Mac::Frame::kVersion2015;
}
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
else if (neighbor->IsEnhAckProbingActive())
{
aTxFrameInfo.mVersion = Mac::Frame::kVersion2015;
}
#endif
#endif // (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE) || OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
// || OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if ((aMessage != nullptr) && aMessage->IsTimeSync())
{
aTxFrameInfo.mAppendTimeIe = true;
aTxFrameInfo.mVersion = Mac::Frame::kVersion2015;
}
#endif
aTxFrameInfo.mEmptyPayload = (aMessage == nullptr) || (aMessage->GetLength() == 0);
#endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Prepare MAC headers
aTxFrameInfo.PrepareHeadersIn(aTxFrame);
OT_UNUSED_VARIABLE(aMessage);
OT_UNUSED_VARIABLE(neighbor);
}
// This method constructs a MAC data from from a given IPv6 message.
//
// This method handles generation of MAC header, mesh header (if
// requested), lowpan compression of IPv6 header, lowpan fragmentation
// header (if message requires fragmentation or if it is explicitly
// requested by setting `aAddFragHeader` to `true`) It uses the
// message offset to construct next fragments. This method enables
// link security when message is MLE type and requires fragmentation.
// It returns the next offset into the message after the prepared
// frame.
//
uint16_t MeshForwarder::PrepareDataFrame(Mac::TxFrame &aFrame,
Message &aMessage,
const Mac::Addresses &aMacAddrs,
bool aAddMeshHeader,
uint16_t aMeshSource,
uint16_t aMeshDest,
bool aAddFragHeader)
{
Mac::TxFrame::Info frameInfo;
uint16_t payloadLength;
uint16_t origMsgOffset;
uint16_t nextOffset;
FrameBuilder frameBuilder;
start:
frameInfo.Clear();
if (aMessage.IsLinkSecurityEnabled())
{
frameInfo.mSecurityLevel = Mac::Frame::kSecurityEncMic32;
if (aMessage.GetSubType() == Message::kSubTypeJoinerEntrust)
{
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode0;
}
else if (aMessage.IsMleCommand(Mle::kCommandAnnounce))
{
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode2;
}
else
{
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode1;
}
}
frameInfo.mPanIds.SetBothSourceDestination(Get<Mac::Mac>().GetPanId());
if (aMessage.IsSubTypeMle())
{
switch (aMessage.GetMleCommand())
{
case Mle::kCommandAnnounce:
aFrame.SetChannel(aMessage.GetChannel());
aFrame.SetRxChannelAfterTxDone(Get<Mac::Mac>().GetPanChannel());
frameInfo.mPanIds.SetDestination(Mac::kPanIdBroadcast);
break;
case Mle::kCommandDiscoveryRequest:
case Mle::kCommandDiscoveryResponse:
frameInfo.mPanIds.SetDestination(aMessage.GetPanId());
break;
default:
break;
}
}
frameInfo.mType = Mac::Frame::kTypeData;
frameInfo.mAddrs = aMacAddrs;
PrepareMacHeaders(aFrame, frameInfo, &aMessage);
frameBuilder.Init(aFrame.GetPayload(), aFrame.GetMaxPayloadLength());
#if OPENTHREAD_FTD
// Initialize Mesh header
if (aAddMeshHeader)
{
Lowpan::MeshHeader meshHeader;
uint16_t maxPayloadLength;
// Mesh Header frames are forwarded by routers over multiple
// hops to reach a final destination. The forwarding path can
// have routers supporting different radio links with varying
// MTU sizes. Since the originator of the frame does not know the
// path and the MTU sizes of supported radio links by the routers
// in the path, we limit the max payload length of a Mesh Header
// frame to a fixed minimum value (derived from 15.4 radio)
// ensuring it can be handled by any radio link.
//
// Maximum payload length is calculated by subtracting the frame
// header and footer lengths from the MTU size. The footer
// length is derived by removing the `aFrame.GetFcsSize()` and
// then adding the fixed `kMeshHeaderFrameFcsSize` instead
// (updating the FCS size in the calculation of footer length).
maxPayloadLength = kMeshHeaderFrameMtu - aFrame.GetHeaderLength() -
(aFrame.GetFooterLength() - aFrame.GetFcsSize() + kMeshHeaderFrameFcsSize);
frameBuilder.Init(aFrame.GetPayload(), maxPayloadLength);
meshHeader.Init(aMeshSource, aMeshDest, kMeshHeaderHopsLeft);
IgnoreError(meshHeader.AppendTo(frameBuilder));
}
#endif // OPENTHREAD_FTD
// While performing lowpan compression, the message offset may be
// changed to skip over the compressed IPv6 headers, we save the
// original offset and set it back on `aMessage` at the end
// before returning.
origMsgOffset = aMessage.GetOffset();
// Compress IPv6 Header
if (aMessage.GetOffset() == 0)
{
uint16_t fragHeaderOffset;
uint16_t maxFrameLength;
Mac::Addresses macAddrs;
// Before performing lowpan header compression, we reduce the
// max length on `frameBuilder` to reserve bytes for first
// fragment header. This ensures that lowpan compression will
// leave room for a first fragment header. After the lowpan
// header compression is done, we reclaim the reserved bytes
// by setting the max length back to its original value.
fragHeaderOffset = frameBuilder.GetLength();
maxFrameLength = frameBuilder.GetMaxLength();
frameBuilder.SetMaxLength(maxFrameLength - sizeof(Lowpan::FragmentHeader::FirstFrag));
if (aAddMeshHeader)
{
macAddrs.mSource.SetShort(aMeshSource);
macAddrs.mDestination.SetShort(aMeshDest);
}
else
{
macAddrs = aMacAddrs;
}
SuccessOrAssert(Get<Lowpan::Lowpan>().Compress(aMessage, macAddrs, frameBuilder));
frameBuilder.SetMaxLength(maxFrameLength);
payloadLength = aMessage.GetLength() - aMessage.GetOffset();
if (aAddFragHeader || (payloadLength > frameBuilder.GetRemainingLength()))
{
Lowpan::FragmentHeader::FirstFrag firstFragHeader;
if ((!aMessage.IsLinkSecurityEnabled()) && aMessage.IsSubTypeMle())
{
// MLE messages that require fragmentation MUST use
// link-layer security. We enable security and try
// constructing the frame again.
aMessage.SetOffset(0);
aMessage.SetLinkSecurityEnabled(true);
goto start;
}
// Insert Fragment header
if (aMessage.GetDatagramTag() == 0)
{
// Avoid using datagram tag value 0, which indicates the tag has not been set
if (mFragTag == 0)
{
mFragTag++;
}
aMessage.SetDatagramTag(mFragTag++);
}
firstFragHeader.Init(aMessage.GetLength(), static_cast<uint16_t>(aMessage.GetDatagramTag()));
SuccessOrAssert(frameBuilder.Insert(fragHeaderOffset, firstFragHeader));
}
}
else
{
Lowpan::FragmentHeader::NextFrag nextFragHeader;
nextFragHeader.Init(aMessage.GetLength(), static_cast<uint16_t>(aMessage.GetDatagramTag()),
aMessage.GetOffset());
SuccessOrAssert(frameBuilder.Append(nextFragHeader));
payloadLength = aMessage.GetLength() - aMessage.GetOffset();
}
if (payloadLength > frameBuilder.GetRemainingLength())
{
payloadLength = (frameBuilder.GetRemainingLength() & ~0x7);
}
// Copy IPv6 Payload
SuccessOrAssert(frameBuilder.AppendBytesFromMessage(aMessage, aMessage.GetOffset(), payloadLength));
aFrame.SetPayloadLength(frameBuilder.GetLength());
nextOffset = aMessage.GetOffset() + payloadLength;
if (nextOffset < aMessage.GetLength())
{
aFrame.SetFramePending(true);
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
aMessage.SetTimeSync(false);
#endif
}
aMessage.SetOffset(origMsgOffset);
return nextOffset;
}
uint16_t MeshForwarder::PrepareDataFrameWithNoMeshHeader(Mac::TxFrame &aFrame,
Message &aMessage,
const Mac::Addresses &aMacAddrs)
{
return PrepareDataFrame(aFrame, aMessage, aMacAddrs, /* aAddMeshHeader */ false, /* aMeshSource */ 0xffff,
/* aMeshDest */ 0xffff, /* aAddFragHeader */ false);
}
Neighbor *MeshForwarder::UpdateNeighborOnSentFrame(Mac::TxFrame &aFrame,
Error aError,
const Mac::Address &aMacDest,
-14
View File
@@ -436,24 +436,12 @@ private:
#endif
void UpdateEidRlocCacheAndStaleChild(RxInfo &aRxInfo);
Error FrameToMessage(RxInfo &aRxInfo, uint16_t aDatagramSize, Message *&aMessage);
void GetMacSourceAddress(const Ip6::Address &aIp6Addr, Mac::Address &aMacAddr);
Message *PrepareNextDirectTransmission(void);
void HandleMesh(RxInfo &aRxInfo);
void ResolveRoutingLoops(uint16_t aSourceRloc16, uint16_t aDestRloc16);
void HandleFragment(RxInfo &aRxInfo);
void HandleLowpanHc(RxInfo &aRxInfo);
void PrepareMacHeaders(Mac::TxFrame &aTxFrame, Mac::TxFrame::Info &aTxFrameInfo, const Message *aMessage);
uint16_t PrepareDataFrame(Mac::TxFrame &aFrame,
Message &aMessage,
const Mac::Addresses &aMacAddrs,
bool aAddMeshHeader,
uint16_t aMeshSource,
uint16_t aMeshDest,
bool aAddFragHeader);
uint16_t PrepareDataFrameWithNoMeshHeader(Mac::TxFrame &aFrame, Message &aMessage, const Mac::Addresses &aMacAddrs);
void PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address &aMacDest, bool aAckRequest);
#if OPENTHREAD_CONFIG_DELAY_AWARE_QUEUE_MANAGEMENT_ENABLE
Error UpdateEcnOrDrop(Message &aMessage, bool aPreparingToSend);
Error RemoveAgedMessages(void);
@@ -462,7 +450,6 @@ private:
bool IsDirectTxQueueOverMaxFrameThreshold(void) const;
void ApplyDirectTxQueueLimit(Message &aMessage);
#endif
void SendMesh(Message &aMessage, Mac::TxFrame &aFrame);
void SendDestinationUnreachable(uint16_t aMeshSource, const Ip6::Headers &aIp6Headers);
Error UpdateIp6Route(Message &aMessage);
Error UpdateIp6RouteFtd(const Ip6::Header &aIp6Header, Message &aMessage);
@@ -566,7 +553,6 @@ private:
PriorityQueue mSendQueue;
MessageQueue mReassemblyList;
uint16_t mFragTag;
uint16_t mMessageNextOffset;
Message *mSendMessage;
-20
View File
@@ -324,26 +324,6 @@ void MeshForwarder::RemoveDataResponseMessages(void)
}
}
void MeshForwarder::SendMesh(Message &aMessage, Mac::TxFrame &aFrame)
{
Mac::TxFrame::Info frameInfo;
frameInfo.mType = Mac::Frame::kTypeData;
frameInfo.mAddrs = mMacAddrs;
frameInfo.mSecurityLevel = Mac::Frame::kSecurityEncMic32;
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode1;
frameInfo.mPanIds.SetBothSourceDestination(Get<Mac::Mac>().GetPanId());
PrepareMacHeaders(aFrame, frameInfo, &aMessage);
// write payload
OT_ASSERT(aMessage.GetLength() <= aFrame.GetMaxPayloadLength());
aMessage.ReadBytes(0, aFrame.GetPayload(), aMessage.GetLength());
aFrame.SetPayloadLength(aMessage.GetLength());
mMessageNextOffset = aMessage.GetLength();
}
Error MeshForwarder::UpdateMeshRoute(Message &aMessage)
{
Error error = kErrorNone;
+373
View File
@@ -0,0 +1,373 @@
/*
* Copyright (c) 2025, 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 implementation of `MessageFramer`.
*/
#include "message_framer.hpp"
#include "instance/instance.hpp"
namespace ot {
MessageFramer::MessageFramer(Instance &aInstance)
: InstanceLocator(aInstance)
{
mFragTag = Random::NonCrypto::GetUint16();
}
void MessageFramer::DetermineMacSourceAddress(const Ip6::Address &aIp6Addr, Mac::Addresses &aMacAddrs) const
{
aMacAddrs.mSource.SetExtendedFromIid(aIp6Addr.GetIid());
if (aMacAddrs.mSource.GetExtended() != Get<Mac::Mac>().GetExtAddress())
{
aMacAddrs.mSource.SetShort(Get<Mac::Mac>().GetShortAddress());
}
}
void MessageFramer::PrepareMacHeaders(Mac::TxFrame &aTxFrame, Mac::TxFrame::Info &aTxFrameInfo, const Message *aMessage)
{
const Neighbor *neighbor;
aTxFrameInfo.mVersion = Mac::Frame::kVersion2006;
#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
#if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE) || OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || \
OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Determine frame version and Header IE entries
neighbor = Get<NeighborTable>().FindNeighbor(aTxFrameInfo.mAddrs.mDestination);
if (neighbor == nullptr)
{
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
else if (Get<Mac::Mac>().IsCslEnabled())
{
aTxFrameInfo.mAppendCslIe = true;
aTxFrameInfo.mVersion = Mac::Frame::kVersion2015;
}
#endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
else if ((Get<ChildTable>().Contains(*neighbor) && static_cast<const Child *>(neighbor)->IsCslSynchronized()))
{
aTxFrameInfo.mVersion = Mac::Frame::kVersion2015;
}
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
else if (neighbor->IsEnhAckProbingActive())
{
aTxFrameInfo.mVersion = Mac::Frame::kVersion2015;
}
#endif
#endif // (OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE) || OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
// || OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
if ((aMessage != nullptr) && aMessage->IsTimeSync())
{
aTxFrameInfo.mAppendTimeIe = true;
aTxFrameInfo.mVersion = Mac::Frame::kVersion2015;
}
#endif
aTxFrameInfo.mEmptyPayload = (aMessage == nullptr) || (aMessage->GetLength() == 0);
#endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Prepare MAC headers
aTxFrameInfo.PrepareHeadersIn(aTxFrame);
OT_UNUSED_VARIABLE(aMessage);
OT_UNUSED_VARIABLE(neighbor);
}
void MessageFramer::PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address &aMacDest, bool aAckRequest)
{
Mac::TxFrame::Info frameInfo;
frameInfo.mAddrs.mSource.SetShort(Get<Mac::Mac>().GetShortAddress());
if (frameInfo.mAddrs.mSource.IsShortAddrInvalid() || aMacDest.IsExtended())
{
frameInfo.mAddrs.mSource.SetExtended(Get<Mac::Mac>().GetExtAddress());
}
frameInfo.mAddrs.mDestination = aMacDest;
frameInfo.mPanIds.SetBothSourceDestination(Get<Mac::Mac>().GetPanId());
frameInfo.mType = Mac::Frame::kTypeData;
frameInfo.mSecurityLevel = Mac::Frame::kSecurityEncMic32;
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode1;
PrepareMacHeaders(aFrame, frameInfo, nullptr);
aFrame.SetAckRequest(aAckRequest);
aFrame.SetPayloadLength(0);
}
uint16_t MessageFramer::PrepareFrame(Mac::TxFrame &aFrame,
Message &aMessage,
const Mac::Addresses &aMacAddrs,
bool aAddMeshHeader,
uint16_t aMeshSource,
uint16_t aMeshDest,
bool aAddFragHeader)
{
Mac::TxFrame::Info frameInfo;
uint16_t payloadLength;
uint16_t origMsgOffset;
uint16_t nextOffset;
FrameBuilder frameBuilder;
start:
frameInfo.Clear();
if (aMessage.IsLinkSecurityEnabled())
{
frameInfo.mSecurityLevel = Mac::Frame::kSecurityEncMic32;
if (aMessage.GetSubType() == Message::kSubTypeJoinerEntrust)
{
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode0;
}
else if (aMessage.IsMleCommand(Mle::kCommandAnnounce))
{
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode2;
}
else
{
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode1;
}
}
frameInfo.mPanIds.SetBothSourceDestination(Get<Mac::Mac>().GetPanId());
if (aMessage.IsSubTypeMle())
{
switch (aMessage.GetMleCommand())
{
case Mle::kCommandAnnounce:
aFrame.SetChannel(aMessage.GetChannel());
aFrame.SetRxChannelAfterTxDone(Get<Mac::Mac>().GetPanChannel());
frameInfo.mPanIds.SetDestination(Mac::kPanIdBroadcast);
break;
case Mle::kCommandDiscoveryRequest:
case Mle::kCommandDiscoveryResponse:
frameInfo.mPanIds.SetDestination(aMessage.GetPanId());
break;
default:
break;
}
}
frameInfo.mType = Mac::Frame::kTypeData;
frameInfo.mAddrs = aMacAddrs;
PrepareMacHeaders(aFrame, frameInfo, &aMessage);
frameBuilder.Init(aFrame.GetPayload(), aFrame.GetMaxPayloadLength());
#if OPENTHREAD_FTD
// Initialize Mesh header
if (aAddMeshHeader)
{
Lowpan::MeshHeader meshHeader;
uint16_t maxPayloadLength;
// Mesh Header frames are forwarded by routers over multiple
// hops to reach a final destination. The forwarding path can
// have routers supporting different radio links with varying
// MTU sizes. Since the originator of the frame does not know the
// path and the MTU sizes of supported radio links by the routers
// in the path, we limit the max payload length of a Mesh Header
// frame to a fixed minimum value (derived from 15.4 radio)
// ensuring it can be handled by any radio link.
//
// Maximum payload length is calculated by subtracting the frame
// header and footer lengths from the MTU size. The footer
// length is derived by removing the `aFrame.GetFcsSize()` and
// then adding the fixed `kMeshHeaderFrameFcsSize` instead
// (updating the FCS size in the calculation of footer length).
maxPayloadLength = kMeshHeaderFrameMtu - aFrame.GetHeaderLength() -
(aFrame.GetFooterLength() - aFrame.GetFcsSize() + kMeshHeaderFrameFcsSize);
frameBuilder.Init(aFrame.GetPayload(), maxPayloadLength);
meshHeader.Init(aMeshSource, aMeshDest, kMeshHeaderHopsLeft);
IgnoreError(meshHeader.AppendTo(frameBuilder));
}
#endif // OPENTHREAD_FTD
// While performing lowpan compression, the message offset may be
// changed to skip over the compressed IPv6 headers, we save the
// original offset and set it back on `aMessage` at the end
// before returning.
origMsgOffset = aMessage.GetOffset();
// Compress IPv6 Header
if (aMessage.GetOffset() == 0)
{
uint16_t fragHeaderOffset;
uint16_t maxFrameLength;
Mac::Addresses macAddrs;
// Before performing lowpan header compression, we reduce the
// max length on `frameBuilder` to reserve bytes for first
// fragment header. This ensures that lowpan compression will
// leave room for a first fragment header. After the lowpan
// header compression is done, we reclaim the reserved bytes
// by setting the max length back to its original value.
fragHeaderOffset = frameBuilder.GetLength();
maxFrameLength = frameBuilder.GetMaxLength();
frameBuilder.SetMaxLength(maxFrameLength - sizeof(Lowpan::FragmentHeader::FirstFrag));
if (aAddMeshHeader)
{
macAddrs.mSource.SetShort(aMeshSource);
macAddrs.mDestination.SetShort(aMeshDest);
}
else
{
macAddrs = aMacAddrs;
}
SuccessOrAssert(Get<Lowpan::Lowpan>().Compress(aMessage, macAddrs, frameBuilder));
frameBuilder.SetMaxLength(maxFrameLength);
payloadLength = aMessage.GetLength() - aMessage.GetOffset();
if (aAddFragHeader || (payloadLength > frameBuilder.GetRemainingLength()))
{
Lowpan::FragmentHeader::FirstFrag firstFragHeader;
if ((!aMessage.IsLinkSecurityEnabled()) && aMessage.IsSubTypeMle())
{
// MLE messages that require fragmentation MUST use
// link-layer security. We enable security and try
// constructing the frame again.
aMessage.SetOffset(0);
aMessage.SetLinkSecurityEnabled(true);
goto start;
}
// Insert Fragment header
if (aMessage.GetDatagramTag() == 0)
{
// Avoid using datagram tag value 0, which indicates the tag has not been set
if (mFragTag == 0)
{
mFragTag++;
}
aMessage.SetDatagramTag(mFragTag++);
}
firstFragHeader.Init(aMessage.GetLength(), static_cast<uint16_t>(aMessage.GetDatagramTag()));
SuccessOrAssert(frameBuilder.Insert(fragHeaderOffset, firstFragHeader));
}
}
else
{
Lowpan::FragmentHeader::NextFrag nextFragHeader;
nextFragHeader.Init(aMessage.GetLength(), static_cast<uint16_t>(aMessage.GetDatagramTag()),
aMessage.GetOffset());
SuccessOrAssert(frameBuilder.Append(nextFragHeader));
payloadLength = aMessage.GetLength() - aMessage.GetOffset();
}
if (payloadLength > frameBuilder.GetRemainingLength())
{
payloadLength = (frameBuilder.GetRemainingLength() & ~0x7);
}
// Copy IPv6 Payload
SuccessOrAssert(frameBuilder.AppendBytesFromMessage(aMessage, aMessage.GetOffset(), payloadLength));
aFrame.SetPayloadLength(frameBuilder.GetLength());
nextOffset = aMessage.GetOffset() + payloadLength;
if (nextOffset < aMessage.GetLength())
{
aFrame.SetFramePending(true);
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
aMessage.SetTimeSync(false);
#endif
}
aMessage.SetOffset(origMsgOffset);
return nextOffset;
}
#if OPENTHREAD_FTD
uint16_t MessageFramer::PrepareMeshFrame(Mac::TxFrame &aFrame, Message &aMessage, const Mac::Addresses &aMacAddrs)
{
Mac::TxFrame::Info frameInfo;
frameInfo.mType = Mac::Frame::kTypeData;
frameInfo.mAddrs = aMacAddrs;
frameInfo.mSecurityLevel = Mac::Frame::kSecurityEncMic32;
frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode1;
frameInfo.mPanIds.SetBothSourceDestination(Get<Mac::Mac>().GetPanId());
PrepareMacHeaders(aFrame, frameInfo, &aMessage);
// write payload
OT_ASSERT(aMessage.GetLength() <= aFrame.GetMaxPayloadLength());
aMessage.ReadBytes(0, aFrame.GetPayload(), aMessage.GetLength());
aFrame.SetPayloadLength(aMessage.GetLength());
return aMessage.GetLength();
}
#endif // OPENTHREAD_FTD
} // namespace ot
+141
View File
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2025, 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 `MessageFramer`.
*/
#ifndef MESSAGE_FRAMER_HPP_
#define MESSAGE_FRAMER_HPP_
#include "openthread-core-config.h"
#include "common/locator.hpp"
#include "common/message.hpp"
#include "common/non_copyable.hpp"
#include "mac/mac_frame.hpp"
#include "mac/mac_types.hpp"
namespace ot {
class DataPollSender;
class MessageFramer : public InstanceLocator, private NonCopyable
{
friend class DataPollSender;
public:
/**
* Initializes the `MessageFramer`.
*
* @param[in] aInstance The OpenThread instance.
*/
explicit MessageFramer(Instance &aInstance);
/**
* Determines the MAC source address for an IPv6 message transmission based on the source IPv6 address used.
*
* @param[in] aIp6Addr The message's source IPv6 address.
* @param[out] aMacAddrs The `Mac::Addresses` object to update. Only the `mSource` field will be updated.
*/
void DetermineMacSourceAddress(const Ip6::Address &aIp6Addr, Mac::Addresses &aMacAddrs) const;
/**
* Prepares an empty MAC data frame.
*
* For MAC source, device's MAC address will be used.
*
* @param[out] aFrame A MAC `TxFrame` to populate.
* @param[in] aMacDest The MAC destination address to use.
* @param[in] aAckRequest A boolean to indicate whether or not set the `AckRequest` flag.
*/
void PrepareEmptyFrame(Mac::TxFrame &aFrame, const Mac::Address &aMacDest, bool aAckRequest);
/**
* Prepares a MAC data frame from a given IPv6 message.
*
* This method handles the generation of the MAC headers, mesh header (if requested), 6LoWPAN header compression,
* and fragmentation header.
*
* If the message requires fragmentation or if @p aAddFragHeader is set to `true`, a fragmentation header will be
* included. The method uses the `aMessage.GetOffset()` to construct subsequent fragments.
*
* This method also handles enabling link-layer security. If the message is an MLE message and requires
* fragmentation, link-layer security is enabled on the message, and the frame to be prepared again.
*
* @param[out] aFrame A MAC `TxFrame` to populate.
* @param[in] aMessage The IPv6 message.
* @param[in] aMacAddrs The MAC source and destination addresses.
* @param[in] aAddMeshHeader A boolean indicating whether to add a mesh header.
* @param[in] aMeshSource The Mesh Header source RLOC16 (used if @p aAddMeshHeader is true).
* @param[in] aMeshDest The Mesh Header destination RLOC16 (used if @p aAddMeshHeader is true).
* @param[in] aAddFragHeader A boolean to force adding a fragmentation header.
*
* @returns The next offset into @p aMessage after the prepared frame.
*/
uint16_t PrepareFrame(Mac::TxFrame &aFrame,
Message &aMessage,
const Mac::Addresses &aMacAddrs,
bool aAddMeshHeader = false,
uint16_t aMeshSource = 0,
uint16_t aMeshDest = 0,
bool aAddFragHeader = false);
#if OPENTHREAD_FTD
/**
* Prepares a MAC data frame from a given 6LoWPAN Mesh message.
*
* @param[out] aFrame A MAC `TxFrame` to populate.
* @param[in] aMessage The 6LoWPAN Mesh message.
* @param[in] aMacAddrs The MAC source and destination addresses.
*
* @returns The next offset into @p aMessage after the prepared frame.
*/
uint16_t PrepareMeshFrame(Mac::TxFrame &aFrame, Message &aMessage, const Mac::Addresses &aMacAddrs);
#endif // OPENTHREAD_FTD
private:
static constexpr uint8_t kMeshHeaderFrameMtu = OT_RADIO_FRAME_MAX_SIZE; // Max MTU with a Mesh Header frame.
static constexpr uint8_t kMeshHeaderFrameFcsSize = sizeof(uint16_t); // Frame FCS size for Mesh Header frame.
// Hops left to use in lowpan mesh header: We use `kMaxRouteCost` as
// max hops between routers within Thread mesh. We then add two
// for possibility of source or destination being a child
// (requiring one hop) and one as additional guard increment.
static constexpr uint8_t kMeshHeaderHopsLeft = Mle::kMaxRouteCost + 3;
void PrepareMacHeaders(Mac::TxFrame &aTxFrame, Mac::TxFrame::Info &aTxFrameInfo, const Message *aMessage);
uint16_t mFragTag;
};
} // namespace ot
#endif // MESSAGE_FRAMER_HPP_