diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index b3a9900bd..81c952086 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -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", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4ce375c6e..c88f371e8 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -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 diff --git a/src/core/instance/instance.cpp b/src/core/instance/instance.cpp index 672dbed1e..2c01d26fe 100644 --- a/src/core/instance/instance.cpp +++ b/src/core/instance/instance.cpp @@ -145,6 +145,7 @@ Instance::Instance(void) , mKeyManager(*this) , mLowpan(*this) , mMac(*this) + , mMessageFramer(*this) , mMeshForwarder(*this) , mMle(*this) , mDiscoverScanner(*this) diff --git a/src/core/instance/instance.hpp b/src/core/instance/instance.hpp index e99148500..b3f19f8fc 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -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 diff --git a/src/core/mac/data_poll_sender.cpp b/src/core/mac/data_poll_sender.cpp index f434e4ff6..3836f9d9d 100644 --- a/src/core/mac/data_poll_sender.cpp +++ b/src/core/mac/data_poll_sender.cpp @@ -571,7 +571,7 @@ Mac::TxFrame *DataPollSender::PrepareDataRequest(Mac::TxFrames &aTxFrames) frameInfo.mSecurityLevel = Mac::Frame::kSecurityEncMic32; frameInfo.mKeyIdMode = Mac::Frame::kKeyIdMode1; - Get().PrepareMacHeaders(*frame, frameInfo, nullptr); + Get().PrepareMacHeaders(*frame, frameInfo, nullptr); #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE if (frame->HasCslIe()) diff --git a/src/core/thread/indirect_sender.cpp b/src/core/thread/indirect_sender.cpp index 762009b51..b418655c8 100644 --- a/src/core/thread/indirect_sender.cpp +++ b/src/core/thread/indirect_sender.cpp @@ -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().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().GetMacSourceAddress(ip6Header.GetSource(), macAddrs.mSource); + Get().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().PrepareDataFrameWithNoMeshHeader(aFrame, aMessage, macAddrs); + aContext.mMessageNextOffset = Get().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().PrepareEmptyFrame(aFrame, macDest, aAckRequest); +exit: + return error; } void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, diff --git a/src/core/thread/indirect_sender.hpp b/src/core/thread/indirect_sender.hpp index fcc120f93..2b3b873f2 100644 --- a/src/core/thread/indirect_sender.hpp +++ b/src/core/thread/indirect_sender.hpp @@ -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); diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp index e53e048a2..05b11dd80 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -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().GetShortAddress()); - - if (frameInfo.mAddrs.mSource.IsShortAddrInvalid() || aMacDest.IsExtended()) - { - frameInfo.mAddrs.mSource.SetExtended(Get().GetExtAddress()); - } - - frameInfo.mAddrs.mDestination = aMacDest; - frameInfo.mPanIds.SetBothSourceDestination(Get().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().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().GetExtAddress()) - { - aMacAddr.SetShort(Get().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().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().GetParent().GetRloc16()); - PrepareEmptyFrame(*frame, macDestAddr, /* aAckRequest */ true); + Get().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().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().FindNeighbor(aTxFrameInfo.mAddrs.mDestination); - - if (neighbor == nullptr) - { - } -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - else if (Get().IsCslEnabled()) - { - aTxFrameInfo.mAppendCslIe = true; - aTxFrameInfo.mVersion = Mac::Frame::kVersion2015; - } -#endif -#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE - else if ((Get().Contains(*neighbor) && static_cast(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().GetPanId()); - - if (aMessage.IsSubTypeMle()) - { - switch (aMessage.GetMleCommand()) - { - case Mle::kCommandAnnounce: - aFrame.SetChannel(aMessage.GetChannel()); - aFrame.SetRxChannelAfterTxDone(Get().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().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(aMessage.GetDatagramTag())); - SuccessOrAssert(frameBuilder.Insert(fragHeaderOffset, firstFragHeader)); - } - } - else - { - Lowpan::FragmentHeader::NextFrag nextFragHeader; - - nextFragHeader.Init(aMessage.GetLength(), static_cast(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, diff --git a/src/core/thread/mesh_forwarder.hpp b/src/core/thread/mesh_forwarder.hpp index 1ef11cc15..d3425670f 100644 --- a/src/core/thread/mesh_forwarder.hpp +++ b/src/core/thread/mesh_forwarder.hpp @@ -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; diff --git a/src/core/thread/mesh_forwarder_ftd.cpp b/src/core/thread/mesh_forwarder_ftd.cpp index 2fa63becb..249523d60 100644 --- a/src/core/thread/mesh_forwarder_ftd.cpp +++ b/src/core/thread/mesh_forwarder_ftd.cpp @@ -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().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; diff --git a/src/core/thread/message_framer.cpp b/src/core/thread/message_framer.cpp new file mode 100644 index 000000000..35243d3df --- /dev/null +++ b/src/core/thread/message_framer.cpp @@ -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().GetExtAddress()) + { + aMacAddrs.mSource.SetShort(Get().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().FindNeighbor(aTxFrameInfo.mAddrs.mDestination); + + if (neighbor == nullptr) + { + } +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + else if (Get().IsCslEnabled()) + { + aTxFrameInfo.mAppendCslIe = true; + aTxFrameInfo.mVersion = Mac::Frame::kVersion2015; + } +#endif +#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + else if ((Get().Contains(*neighbor) && static_cast(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().GetShortAddress()); + + if (frameInfo.mAddrs.mSource.IsShortAddrInvalid() || aMacDest.IsExtended()) + { + frameInfo.mAddrs.mSource.SetExtended(Get().GetExtAddress()); + } + + frameInfo.mAddrs.mDestination = aMacDest; + frameInfo.mPanIds.SetBothSourceDestination(Get().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().GetPanId()); + + if (aMessage.IsSubTypeMle()) + { + switch (aMessage.GetMleCommand()) + { + case Mle::kCommandAnnounce: + aFrame.SetChannel(aMessage.GetChannel()); + aFrame.SetRxChannelAfterTxDone(Get().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().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(aMessage.GetDatagramTag())); + SuccessOrAssert(frameBuilder.Insert(fragHeaderOffset, firstFragHeader)); + } + } + else + { + Lowpan::FragmentHeader::NextFrag nextFragHeader; + + nextFragHeader.Init(aMessage.GetLength(), static_cast(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().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 diff --git a/src/core/thread/message_framer.hpp b/src/core/thread/message_framer.hpp new file mode 100644 index 000000000..97569daf4 --- /dev/null +++ b/src/core/thread/message_framer.hpp @@ -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_