diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index b3699823482..1b8fe97abca 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -640,6 +640,8 @@ openthread_core_files = [ "thread/dua_manager.hpp", "thread/energy_scan_server.cpp", "thread/energy_scan_server.hpp", + "thread/enh_csl_neighbor_table.cpp", + "thread/enh_csl_neighbor_table.hpp", "thread/indirect_sender.cpp", "thread/indirect_sender.hpp", "thread/indirect_sender_frame_context.hpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index af0046b64cd..c0fab253e1a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -214,6 +214,7 @@ set(COMMON_SOURCES thread/discover_scanner.cpp thread/dua_manager.cpp thread/energy_scan_server.cpp + thread/enh_csl_neighbor_table.cpp thread/indirect_sender.cpp thread/key_manager.cpp thread/link_metrics.cpp diff --git a/src/core/common/locator.hpp b/src/core/common/locator.hpp index 8b1f8221742..0e8c0e04521 100644 --- a/src/core/common/locator.hpp +++ b/src/core/common/locator.hpp @@ -81,7 +81,7 @@ template class GetProvider * * @returns A reference to the `Type` object of the instance. */ - template inline Type &Get(void) const; // Implemented in `locator_getters.hpp`. + template inline Type &Get(void) const; protected: GetProvider(void) = default; diff --git a/src/core/common/message.hpp b/src/core/common/message.hpp index 4441af5804c..c94d3a26273 100644 --- a/src/core/common/message.hpp +++ b/src/core/common/message.hpp @@ -202,6 +202,9 @@ class Buffer : public otMessageBuffer, public LinkedListEntry #endif #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE bool mTimeSync : 1; // Whether the message is also used for time sync purpose. +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + bool mIsEnhCslNeighbor : 1; // Whether the message is for an enhanced CSL neighbor. #endif uint8_t mPriority : 2; // The message priority level (higher value is higher priority). uint8_t mOrigin : 2; // The origin of the message. @@ -1469,6 +1472,23 @@ class Message : public otMessage, public Buffer, public GetProvider #endif // #if OPENTHREAD_CONFIG_MULTI_RADIO +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + /** + * Indicates whether the message is destined for an enhanced CSL neighbor. + * + * @retval TRUE If the message is destined for an enhanced CSL neighbor. + * @retval FALSE If the message is not destined for an enhanced CSL neighbor. + */ + bool IsForEnhancedCslNeighbor(void) const { return GetMetadata().mIsEnhCslNeighbor; } + + /** + * Sets whether the message is destined for an enhanced CSL neighbor. + * + * @param[in] aIsEnhCslNeighbor TRUE if the message is destined for an enhanced CSL neighbor, FALSE otherwise. + */ + void SetForEnhancedCslNeighbor(bool aIsEnhCslNeighbor) { GetMetadata().mIsEnhCslNeighbor = aIsEnhCslNeighbor; } +#endif + protected: class ConstIterator : public ItemPtrIterator { diff --git a/src/core/config/mac.h b/src/core/config/mac.h index d2359a91db2..84db7472804 100644 --- a/src/core/config/mac.h +++ b/src/core/config/mac.h @@ -558,6 +558,25 @@ #define OPENTHREAD_CONFIG_MAC_DATA_POLL_TIMEOUT 100 #endif +/** + * @def OPENTHREAD_CONFIG_MAC_CSL_WAKEUP_INTERVAL + * + * Periodicity of wake-up frame transmission by WC (in units of 10 symbols). + */ +#ifndef OPENTHREAD_CONFIG_MAC_CSL_WAKEUP_INTERVAL +#define OPENTHREAD_CONFIG_MAC_CSL_WAKEUP_INTERVAL 47 +#endif + +/** + * @def OPENTHREAD_CONFIG_MAC_ENH_CSL_TX_ATTEMPTS + * + * Maximum number of TX attempts for the enhanced CSL communication before considering the peer de-synchronized. + * + */ +#ifndef OPENTHREAD_CONFIG_MAC_ENH_CSL_TX_ATTEMPTS +#define OPENTHREAD_CONFIG_MAC_ENH_CSL_TX_ATTEMPTS 8 +#endif + /** * @} */ diff --git a/src/core/mac/data_poll_handler.cpp b/src/core/mac/data_poll_handler.cpp index b3d97597007..6eae5d0ae4c 100644 --- a/src/core/mac/data_poll_handler.cpp +++ b/src/core/mac/data_poll_handler.cpp @@ -255,7 +255,7 @@ void DataPollHandler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, OT_ASSERT(false); } - Get().HandleSentFrameToChild(aFrame, mFrameContext, aError, aChild); + Get().HandleSentFrameToCslNeighbor(aFrame, mFrameContext, aError, aChild); exit: return; diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index ada1c940558..18e7731fe13 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -1655,7 +1655,8 @@ Error Mac::ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neig { uint32_t sequence; - // TODO: Avoid generating a new key if a wake-up frame was recently received already + // Avoid generating a new key if a wake-up frame was recently received already + VerifyOrExit(!Get().IsWakeupParentPresent(), error = kErrorInvalidState); IgnoreError(aFrame.GetKeyId(keyid)); sequence = BigEndian::ReadUint32(aFrame.GetKeySource()); @@ -2472,22 +2473,19 @@ bool Mac::IsCslSupported(void) const void Mac::ProcessCsl(const RxFrame &aFrame, const Address &aSrcAddr) { - CslNeighbor *neighbor = nullptr; + CslNeighbor *neighbor = Get().FindCslNeighbor(aSrcAddr); const CslIe *csl; + VerifyOrExit(neighbor != nullptr); VerifyOrExit(aFrame.IsVersion2015() && aFrame.GetSecurityEnabled()); csl = aFrame.GetCslIe(); VerifyOrExit(csl != nullptr); -#if OPENTHREAD_FTD - neighbor = Get().FindChild(aSrcAddr, Child::kInStateAnyExceptInvalid); -#else - OT_UNUSED_VARIABLE(aSrcAddr); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + VerifyOrExit(neighbor->GetExtAddress() == aSrcAddr.GetExtended()); #endif - VerifyOrExit(neighbor != nullptr); - VerifyOrExit(csl->GetPeriod() >= kMinCslIePeriod); neighbor->SetCslPeriod(csl->GetPeriod()); @@ -2620,7 +2618,8 @@ void Mac::UpdateWakeupListening(void) Error Mac::HandleWakeupFrame(const RxFrame &aFrame) { - Error error = kErrorNone; + Error error = kErrorNone; + constexpr uint32_t kWakeupIntervalUs = kDefaultWedListenInterval * kUsPerTenSymbols; const ConnectionIe *connectionIe; uint32_t rvTimeUs; uint64_t rvTimestampUs; @@ -2628,6 +2627,8 @@ Error Mac::HandleWakeupFrame(const RxFrame &aFrame) uint64_t radioNowUs; uint8_t retryInterval; uint8_t retryCount; + Address parentAddress; + CslNeighbor *parent; VerifyOrExit(mWakeupListenEnabled && aFrame.IsWakeupFrame()); connectionIe = aFrame.GetConnectionIe(); @@ -2662,8 +2663,23 @@ Error Mac::HandleWakeupFrame(const RxFrame &aFrame) // Stop receiving more wake up frames IgnoreError(SetWakeupListenEnabled(false)); - // TODO: start MLE attach process with the WC - OT_UNUSED_VARIABLE(attachDelayMs); + IgnoreError(aFrame.GetSrcAddr(parentAddress)); + Get().AddWakeupParent(parentAddress.GetExtended(), TimerMilli::GetNow() + attachDelayMs, + kWakeupIntervalUs * retryInterval * retryCount / 1000); + + parent = Get().GetWakeupParent(); + OT_ASSERT(parent != nullptr); + parent->SetCslPeriod(kDefaultWedListenInterval * retryInterval); + parent->SetCslPhase(0); + parent->SetCslSynchronized(true); + parent->SetCslLastHeard(TimerMilli::GetNow()); + // Rendezvous time is the time when the WC begins listening for the connection request from the awakened WED. + // Since EnhCslSender schedules a frame's PHR at `LastRxTimestamp + Phase + n*Period`, increase the timestamp + // by SHR length and the CSL uncertainty to make sure SHR begins while the WC is already listening. + parent->SetLastRxTimestamp(rvTimestampUs + kRadioHeaderShrDuration + Get().GetCslUncertainty() * 10); + parent->SetEnhCslMaxTxAttempts(retryCount); + + Get().AttachToWakeupParent(); exit: return error; diff --git a/src/core/mac/mac.hpp b/src/core/mac/mac.hpp index 109bbbb65d8..e6ec114d53d 100644 --- a/src/core/mac/mac.hpp +++ b/src/core/mac/mac.hpp @@ -97,6 +97,7 @@ constexpr uint16_t kMinCslIePeriod = OPENTHREAD_CONFIG_MAC_CSL_MIN_PERIOD; constexpr uint32_t kDefaultWedListenInterval = OPENTHREAD_CONFIG_WED_LISTEN_INTERVAL; constexpr uint32_t kDefaultWedListenDuration = OPENTHREAD_CONFIG_WED_LISTEN_DURATION; +constexpr uint16_t kDefaultWakeupInterval = OPENTHREAD_CONFIG_MAC_CSL_WAKEUP_INTERVAL; /** * Defines the function pointer called on receiving an IEEE 802.15.4 Beacon during an Active Scan. diff --git a/src/core/thread/csl_tx_scheduler.hpp b/src/core/thread/csl_tx_scheduler.hpp index 13fb800e27c..2524b69fdf4 100644 --- a/src/core/thread/csl_tx_scheduler.hpp +++ b/src/core/thread/csl_tx_scheduler.hpp @@ -64,6 +64,9 @@ class CslTxScheduler : public InstanceLocator, private NonCopyable public: static constexpr uint8_t kMaxCslTriggeredTxAttempts = OPENTHREAD_CONFIG_MAC_MAX_TX_ATTEMPTS_INDIRECT_POLLS; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + static constexpr uint8_t kMaxEnhCslTriggeredTxAttempts = OPENTHREAD_CONFIG_MAC_ENH_CSL_TX_ATTEMPTS; +#endif /** * Defines all the neighbor info required for scheduling CSL transmissions. @@ -98,6 +101,15 @@ class CslTxScheduler : public InstanceLocator, private NonCopyable uint64_t GetLastRxTimestamp(void) const { return mLastRxTimestamp; } void SetLastRxTimestamp(uint64_t aLastRxTimestamp) { mLastRxTimestamp = aLastRxTimestamp; } +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + uint8_t GetEnhCslMaxTxAttempts(void) const + { + return mCslMaxTxAttempts != 0 ? mCslMaxTxAttempts : kMaxEnhCslTriggeredTxAttempts; + } + void SetEnhCslMaxTxAttempts(uint8_t txAttempts) { mCslMaxTxAttempts = txAttempts; } + void ResetEnhCslMaxTxAttempts() { mCslMaxTxAttempts = 0; } +#endif + private: uint8_t mCslTxAttempts : 7; ///< Number of CSL triggered tx attempts. bool mCslSynchronized : 1; ///< Indicates whether or not the child is CSL synchronized. @@ -153,6 +165,10 @@ class CslTxScheduler : public InstanceLocator, private NonCopyable uint64_t mLastRxTimestamp; ///< Radio clock time when last frame containing CSL IE was received, in microseconds. +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + uint8_t mCslMaxTxAttempts; ///< Override for the maximum number of enhanced CSL triggered tx attempts. +#endif + static_assert(kMaxCslTriggeredTxAttempts < (1 << 7), "mCslTxAttempts cannot fit max!"); }; diff --git a/src/core/thread/enh_csl_neighbor_table.cpp b/src/core/thread/enh_csl_neighbor_table.cpp new file mode 100644 index 00000000000..0370b33ddbe --- /dev/null +++ b/src/core/thread/enh_csl_neighbor_table.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024, 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 enhanced CSL neighbor table. + */ + +#include "enh_csl_neighbor_table.hpp" + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + +#include "instance/instance.hpp" + +namespace ot { + +CslNeighborTable::CslNeighborTable(Instance &aInstance) +{ + for (CslNeighbor &cslNeighbor : mCslNeighbors) + { + cslNeighbor.Init(aInstance); + } +} + +CslNeighbor *CslNeighborTable::GetNewCslNeighbor(void) { return mCslNeighbors; } + +CslNeighbor *CslNeighborTable::GetFirstCslNeighbor(void) { return mCslNeighbors; } + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE diff --git a/src/core/thread/enh_csl_neighbor_table.hpp b/src/core/thread/enh_csl_neighbor_table.hpp new file mode 100644 index 00000000000..d8d8855cea6 --- /dev/null +++ b/src/core/thread/enh_csl_neighbor_table.hpp @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2024, 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 the CSL neighbors table. + */ + +#ifndef ENH_CSL_NEIGHBOR_TABLE_HPP_ +#define ENH_CSL_NEIGHBOR_TABLE_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + +#include "common/const_cast.hpp" +#include "common/iterator_utils.hpp" +#include "common/locator.hpp" +#include "common/non_copyable.hpp" +#include "thread/neighbor.hpp" + +namespace ot { + +/** + * Represents the CSL neighbors table. + */ +class CslNeighborTable : private NonCopyable +{ +public: + /** + * Initializes a `CslNeighborTable` instance. + * + * @param[in] aInstance A reference to the OpenThread instance. + */ + explicit CslNeighborTable(Instance &aInstance); + + /** + * Gets a new/unused `CslNeighbor` entry from the enhanced CSL neighbor table. + * + * @note The returned neighbor entry will be cleared (`memset` to zero). + * + * @returns A pointer to a new `CslNeighbor` entry, or `nullptr` if all `CslNeighbor` entries are in use. + */ + CslNeighbor *GetNewCslNeighbor(void); + + /** + * Gets the first `CslNeighbor` entry in the enhanced CSL neighbor table. + * + * @returns A pointer to the first `CslNeighbor` entry, or `nullptr` if the table is empty. + */ + CslNeighbor *GetFirstCslNeighbor(void); + +private: + // static constexpr uint16_t kMaxCslNeighbors = OPENTHREAD_CONFIG_MLE_MAX_ENH_CSL_NEIGHBORS; + static constexpr uint16_t kMaxCslNeighbors = 1; + + CslNeighbor mCslNeighbors[kMaxCslNeighbors]; +}; + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + +#endif // ENH_CSL_NEIGHBOR_TABLE_HPP_ diff --git a/src/core/thread/indirect_sender.cpp b/src/core/thread/indirect_sender.cpp index b465c8de8cf..ed5c64e4fec 100644 --- a/src/core/thread/indirect_sender.cpp +++ b/src/core/thread/indirect_sender.cpp @@ -90,6 +90,8 @@ void IndirectSender::Stop(void) #if OPENTHREAD_FTD +bool IndirectSender::IsChild(const Neighbor &aNeighbor) const { return Get().Contains(aNeighbor); } + void IndirectSender::AddMessageForSleepyChild(Message &aMessage, Child &aChild) { uint16_t childIndex; @@ -239,7 +241,7 @@ void IndirectSender::RequestMessageUpdate(Child &aChild) if ((curMessage != nullptr) && !curMessage->GetIndirectTxChildMask().Has(Get().GetChildIndex(aChild))) { // Set the indirect message for this child to `nullptr` to ensure - // it is not processed on `HandleSentFrameToChild()` callback. + // it is not processed on `HandleSentFrameToCslNeighbor()` callback. aChild.SetIndirectMessage(nullptr); @@ -301,14 +303,30 @@ void IndirectSender::HandleFrameChangeDone(Child &aChild) return; } -void IndirectSender::UpdateIndirectMessage(Child &aChild) +#endif // OPENTHREAD_FTD + +void IndirectSender::UpdateIndirectMessage(CslNeighbor &aNeighbor) { - Message *message = FindQueuedMessageForSleepyChild(aChild, AcceptAnyMessage); + Message *message = nullptr; + +#if OPENTHREAD_FTD + if (IsChild(aNeighbor)) + { + message = FindQueuedMessageForSleepyChild(static_cast(aNeighbor), AcceptAnyMessage); + } +#endif - aChild.SetWaitingForMessageUpdate(false); - aChild.SetIndirectMessage(message); - aChild.SetIndirectFragmentOffset(0); - aChild.SetIndirectTxSuccess(true); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (message == nullptr) + { + message = FindQueuedMessageForWedNeighbor(aNeighbor); + } +#endif + + aNeighbor.SetWaitingForMessageUpdate(false); + aNeighbor.SetIndirectMessage(message); + aNeighbor.SetIndirectFragmentOffset(0); + aNeighbor.SetIndirectTxSuccess(true); #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE mCslTxScheduler.Update(); @@ -316,13 +334,15 @@ void IndirectSender::UpdateIndirectMessage(Child &aChild) if (message != nullptr) { - Mac::Address childAddress; + Mac::Address neighborAddress; - aChild.GetMacAddress(childAddress); - Get().LogMessage(MeshForwarder::kMessagePrepareIndirect, *message, kErrorNone, &childAddress); + aNeighbor.GetMacAddress(neighborAddress); + Get().LogMessage(MeshForwarder::kMessagePrepareIndirect, *message, kErrorNone, &neighborAddress); } } +#if OPENTHREAD_FTD + Error IndirectSender::PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild) { Error error = kErrorNone; @@ -340,7 +360,7 @@ Error IndirectSender::PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &a switch (message->GetType()) { case Message::kTypeIp6: - aContext.mMessageNextOffset = PrepareDataFrame(aFrame, aChild, *message); + aContext.mMessageNextOffset = PrepareDataFrameForSleepyChild(aFrame, aChild, *message); break; case Message::kTypeSupervision: @@ -356,7 +376,9 @@ Error IndirectSender::PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &a return error; } -uint16_t IndirectSender::PrepareDataFrame(Mac::TxFrame &aFrame, Child &aChild, Message &aMessage) +#endif // OPENTHREAD_FTD + +uint16_t IndirectSender::PrepareDataFrame(Mac::TxFrame &aFrame, CslNeighbor &aNeighbor, Message &aMessage) { Ip6::Header ip6Header; Mac::Addresses macAddrs; @@ -375,22 +397,34 @@ uint16_t IndirectSender::PrepareDataFrame(Mac::TxFrame &aFrame, Child &aChild, M } else { - aChild.GetMacAddress(macAddrs.mDestination); + aNeighbor.GetMacAddress(macAddrs.mDestination); } - // Prepare the data frame from previous child's indirect offset. + // Prepare the data frame from previous neighbor's indirect offset. directTxOffset = aMessage.GetOffset(); - aMessage.SetOffset(aChild.GetIndirectFragmentOffset()); + aMessage.SetOffset(aNeighbor.GetIndirectFragmentOffset()); nextOffset = Get().PrepareDataFrameWithNoMeshHeader(aFrame, aMessage, macAddrs); aMessage.SetOffset(directTxOffset); + return nextOffset; +} + +#if OPENTHREAD_FTD + +uint16_t IndirectSender::PrepareDataFrameForSleepyChild(Mac::TxFrame &aFrame, Child &aChild, Message &aMessage) +{ + uint16_t nextOffset; + + nextOffset = PrepareDataFrame(aFrame, aChild, aMessage); + // 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 `PrepareDataFrameForSleepyChild()` + // method. if (aChild.GetIndirectMessageCount() > 1) { @@ -407,19 +441,31 @@ void IndirectSender::PrepareEmptyFrame(Mac::TxFrame &aFrame, Child &aChild, bool Get().PrepareEmptyFrame(aFrame, macDest, aAckRequest); } -void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, - const FrameContext &aContext, - Error aError, - Child &aChild) +#endif // OPENTHREAD_FTD + +void IndirectSender::HandleSentFrameToCslNeighbor(const Mac::TxFrame &aFrame, + const FrameContext &aContext, + Error aError, + CslNeighbor &aNeighbor) { - Message *message = aChild.GetIndirectMessage(); + Message *message = aNeighbor.GetIndirectMessage(); uint16_t nextOffset = aContext.mMessageNextOffset; +#if OPENTHREAD_FTD + Child *child = nullptr; +#endif + VerifyOrExit(mEnabled); - if (aError == kErrorNone) +#if OPENTHREAD_FTD + if (IsChild(aNeighbor)) + { + child = static_cast(&aNeighbor); + } + + if (aError == kErrorNone && child != nullptr) { - Get().UpdateOnSend(aChild); + Get().UpdateOnSend(*child); } // A zero `nextOffset` indicates that the sent frame is an empty @@ -429,12 +475,13 @@ void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, // support the "source address match" feature and always includes // "frame pending" flag in acks to data poll frames. In such a case, // `IndirectSender` prepares and sends an empty frame to the child - // after it sends a data poll. Here in `HandleSentFrameToChild()` we + // after it sends a data poll. Here in `HandleSentFrameToCslNeighbor()` we // exit quickly if we detect the "send done" is for the empty frame // to ensure we do not update any newly added indirect message after // preparing the empty frame. VerifyOrExit(nextOffset != 0); +#endif // OPENTHREAD_FTD switch (aError) { @@ -445,11 +492,11 @@ void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, case kErrorChannelAccessFailure: case kErrorAbort: - aChild.SetIndirectTxSuccess(false); + aNeighbor.SetIndirectTxSuccess(false); #if OPENTHREAD_CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE // We set the nextOffset to end of message, since there is no need to - // send any remaining fragments in the message to the child, if all tx + // send any remaining fragments in the message to the neighbor, if all tx // attempts of current frame already failed. if (message != nullptr) @@ -465,7 +512,7 @@ void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, if ((message != nullptr) && (nextOffset < message->GetLength())) { - aChild.SetIndirectFragmentOffset(nextOffset); + aNeighbor.SetIndirectFragmentOffset(nextOffset); #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE mCslTxScheduler.Update(); #endif @@ -476,23 +523,27 @@ void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, { // The indirect tx of this message to the child is done. - Error txError = aError; - uint16_t childIndex = Get().GetChildIndex(aChild); + Error txError = aError; Mac::Address macDest; - aChild.SetIndirectMessage(nullptr); - aChild.GetLinkInfo().AddMessageTxStatus(aChild.GetIndirectTxSuccess()); - - // Enable short source address matching after the first indirect - // message transmission attempt to the child. We intentionally do - // not check for successful tx here to address the scenario where - // the child does receive "Child ID Response" but parent misses the - // 15.4 ack from child. If the "Child ID Response" does not make it - // to the child, then the child will need to send a new "Child ID - // Request" which will cause the parent to switch to using long - // address mode for source address matching. + aNeighbor.SetIndirectMessage(nullptr); + aNeighbor.GetLinkInfo().AddMessageTxStatus(aNeighbor.GetIndirectTxSuccess()); - mSourceMatchController.SetSrcMatchAsShort(aChild, true); +#if OPENTHREAD_FTD + if (child != nullptr) + { + // Enable short source address matching after the first indirect + // message transmission attempt to the child. We intentionally do + // not check for successful tx here to address the scenario where + // the child does receive "Child ID Response" but parent misses the + // 15.4 ack from child. If the "Child ID Response" does not make it + // to the child, then the child will need to send a new "Child ID + // Request" which will cause the parent to switch to using long + // address mode for source address matching. + + mSourceMatchController.SetSrcMatchAsShort(*child, true); + } +#endif #if !OPENTHREAD_CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE @@ -500,11 +551,11 @@ void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, // disabled, all fragment frames of a larger message are // sent even if the transmission of an earlier fragment fail. // Note that `GetIndirectTxSuccess() tracks the tx success of - // the entire message to the child, while `txError = aError` + // the entire message to the neighbor, while `txError = aError` // represents the error status of the last fragment frame // transmission. - if (!aChild.GetIndirectTxSuccess() && (txError == kErrorNone)) + if (!aNeighbor.GetIndirectTxSuccess() && (txError == kErrorNone)) { txError = kErrorFailed; } @@ -518,7 +569,7 @@ void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, if (message->GetType() == Message::kTypeIp6) { - if (aChild.GetIndirectTxSuccess()) + if (aNeighbor.GetIndirectTxSuccess()) { Get().mIpCounters.mTxSuccess++; } @@ -528,24 +579,37 @@ void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, } } - if (message->GetIndirectTxChildMask().Has(childIndex)) +#if OPENTHREAD_FTD + if (child != nullptr) { - message->GetIndirectTxChildMask().Remove(childIndex); - mSourceMatchController.DecrementMessageCount(aChild); + uint16_t childIndex = Get().GetChildIndex(*child); + + if (message->GetIndirectTxChildMask().Has(childIndex)) + { + message->GetIndirectTxChildMask().Remove(childIndex); + mSourceMatchController.DecrementMessageCount(*child); + } } +#endif Get().RemoveMessageIfNoPendingTx(*message); } - UpdateIndirectMessage(aChild); + UpdateIndirectMessage(aNeighbor); exit: - if (mEnabled) +#if OPENTHREAD_FTD + if (mEnabled && (child != nullptr)) { ClearMessagesForRemovedChildren(); } +#else + return; +#endif } +#if OPENTHREAD_FTD + void IndirectSender::ClearMessagesForRemovedChildren(void) { for (Child &child : Get().Iterate(Child::kInStateAnyExceptValidOrRestoring)) @@ -573,6 +637,57 @@ bool IndirectSender::AcceptSupervisionMessage(const Message &aMessage) #endif // OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +void IndirectSender::AddMessageForEnhCslNeighbor(Message &aMessage, CslNeighbor &aNeighbor) +{ + OT_UNUSED_VARIABLE(aNeighbor); + + aMessage.SetForEnhancedCslNeighbor(true); +} + +Error IndirectSender::PrepareFrameForEnhCslNeighbor(Mac::TxFrame &aFrame, + FrameContext &aContext, + CslNeighbor &aNeighbor) +{ + Error error = kErrorNone; + Message *message = aNeighbor.GetIndirectMessage(); + + VerifyOrExit(message != nullptr, error = kErrorInvalidState); + + switch (message->GetType()) + { + case Message::kTypeIp6: + aContext.mMessageNextOffset = PrepareDataFrame(aFrame, aNeighbor, *message); + break; + + default: + error = kErrorNotImplemented; + break; + } + +exit: + return error; +} + +Message *IndirectSender::FindQueuedMessageForWedNeighbor(CslNeighbor &aNeighbor) +{ + Message *match = nullptr; + + OT_UNUSED_VARIABLE(aNeighbor); + + for (Message &message : Get().mSendQueue) + { + if (message.IsForEnhancedCslNeighbor()) + { + match = &message; + break; + } + } + + return match; +} +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE Error IndirectSender::PrepareFrameForCslNeighbor(Mac::TxFrame &aFrame, @@ -582,34 +697,27 @@ Error IndirectSender::PrepareFrameForCslNeighbor(Mac::TxFrame &aFrame, Error error = kErrorNotFound; #if OPENTHREAD_FTD - // `CslNeighbor` can only be a `Child` for now, but can be changed later. - error = PrepareFrameForChild(aFrame, aContext, static_cast(aCslNeighbor)); + if (IsChild(aCslNeighbor)) + { + error = PrepareFrameForChild(aFrame, aContext, static_cast(aCslNeighbor)); + VerifyOrExit(error != kErrorNone); + } +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + error = PrepareFrameForEnhCslNeighbor(aFrame, aContext, aCslNeighbor); + ExitNow(); #else OT_UNUSED_VARIABLE(aFrame); OT_UNUSED_VARIABLE(aContext); OT_UNUSED_VARIABLE(aCslNeighbor); + ExitNow(); #endif +exit: return error; } +#endif // OPENTHREAD_CONFIG_CSL_TRANSMITTER_ENABLE -void IndirectSender::HandleSentFrameToCslNeighbor(const Mac::TxFrame &aFrame, - const FrameContext &aContext, - Error aError, - CslNeighbor &aCslNeighbor) -{ -#if OPENTHREAD_FTD - HandleSentFrameToChild(aFrame, aContext, aError, static_cast(aCslNeighbor)); -#else - OT_UNUSED_VARIABLE(aFrame); - OT_UNUSED_VARIABLE(aContext); - OT_UNUSED_VARIABLE(aError); - OT_UNUSED_VARIABLE(aCslNeighbor); -#endif -} - -#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE - -#endif // OPENTHREAD_FTD || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +#endif // OPENTHREAD_FTD || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE } // namespace ot diff --git a/src/core/thread/indirect_sender.hpp b/src/core/thread/indirect_sender.hpp index fcc120f9379..1de58bdc497 100644 --- a/src/core/thread/indirect_sender.hpp +++ b/src/core/thread/indirect_sender.hpp @@ -257,25 +257,38 @@ class IndirectSender : public InstanceLocator, public IndirectSenderBase, privat #endif // OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + /** + * Adds a message for indirect transmission to a WED neighbor. + * + * @param[in] aMessage The message to add. + * @param[in] aNeighbor The neighbor for indirect transmission. + */ + void AddMessageForEnhCslNeighbor(Message &aMessage, CslNeighbor &aCslNeighbor); +#endif + private: #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE // Callbacks from `CslTxScheduler` Error PrepareFrameForCslNeighbor(Mac::TxFrame &aFrame, FrameContext &aContext, CslNeighbor &aCslNeighbor); - void HandleSentFrameToCslNeighbor(const Mac::TxFrame &aFrame, - const FrameContext &aContext, - Error aError, - CslNeighbor &aCslNeighbor); #endif + uint16_t PrepareDataFrame(Mac::TxFrame &aFrame, CslNeighbor &aNeighbor, Message &aMessage); + void HandleSentFrameToCslNeighbor(const Mac::TxFrame &aFrame, + const FrameContext &aContext, + Error aError, + CslNeighbor &aNeighbor); + void UpdateIndirectMessage(CslNeighbor &aNeighbor); + #if OPENTHREAD_FTD + bool IsChild(const Neighbor &aNeighbor) const; + // Callbacks from `DataPollHandler` Error PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild); - 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); + uint16_t PrepareDataFrameForSleepyChild(Mac::TxFrame &aFrame, Child &aChild, Message &aMessage); void PrepareEmptyFrame(Mac::TxFrame &aFrame, Child &aChild, bool aAckRequest); void ClearMessagesForRemovedChildren(void); @@ -283,6 +296,11 @@ class IndirectSender : public InstanceLocator, public IndirectSenderBase, privat static bool AcceptSupervisionMessage(const Message &aMessage); #endif // OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + Error PrepareFrameForEnhCslNeighbor(Mac::TxFrame &aFrame, FrameContext &aContext, CslNeighbor &aNeighbor); + Message *FindQueuedMessageForWedNeighbor(CslNeighbor &aNeighbor); +#endif + bool mEnabled; #if OPENTHREAD_FTD SourceMatchController mSourceMatchController; diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp index 9999b36be59..291499afa2e 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -686,14 +686,27 @@ void MeshForwarder::SetRxOnWhenIdle(bool aRxOnWhenIdle) { Get().SetRxOnWhenIdle(aRxOnWhenIdle); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + // Data polls not allowed in enhanced CSL mode + if (!Get().IsWakeupParentPresent()) +#endif + { + if (aRxOnWhenIdle) + { + mDataPollSender.StopPolling(); + } + else + { + mDataPollSender.StartPolling(); + } + } + if (aRxOnWhenIdle) { - mDataPollSender.StopPolling(); Get().Stop(); } else { - mDataPollSender.StartPolling(); Get().Start(); } } @@ -1083,7 +1096,12 @@ uint16_t MeshForwarder::PrepareDataFrame(Mac::TxFrame &aFrame, if (nextOffset < aMessage.GetLength()) { - aFrame.SetFramePending(true); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (!Get().IsWakeupParentPresent()) +#endif + { + aFrame.SetFramePending(true); + } #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE aMessage.SetTimeSync(false); #endif diff --git a/src/core/thread/mesh_forwarder_ftd.cpp b/src/core/thread/mesh_forwarder_ftd.cpp index 270a41b3074..a765184768e 100644 --- a/src/core/thread/mesh_forwarder_ftd.cpp +++ b/src/core/thread/mesh_forwarder_ftd.cpp @@ -48,6 +48,7 @@ void MeshForwarder::SendMessage(OwnedPtr aMessagePtr) message.SetOffset(0); message.SetDatagramTag(0); message.SetTimestampToNow(); + mSendQueue.Enqueue(message); switch (message.GetType()) @@ -87,14 +88,35 @@ void MeshForwarder::SendMessage(OwnedPtr aMessagePtr) mIndirectSender.AddMessageForSleepyChild(message, child); } } + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (destinedForAll) + { + CslNeighbor *cslNeighbor = Get().GetWakeupParent(); + + if ((cslNeighbor != nullptr) && cslNeighbor->IsCslSynchronized()) + { + mIndirectSender.AddMessageForEnhCslNeighbor(message, *cslNeighbor); + } + } +#endif } } else // Destination is unicast { Neighbor *neighbor = Get().FindNeighbor(destination); - if ((neighbor != nullptr) && !neighbor->IsRxOnWhenIdle() && !message.IsDirectTransmission() && - Get().Contains(*neighbor)) +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + CslNeighbor *cslNeighbor = Get().GetWakeupParent(); + + if ((neighbor == nullptr) && (cslNeighbor != nullptr) && cslNeighbor->IsCslSynchronized()) + { + mIndirectSender.AddMessageForEnhCslNeighbor(message, *cslNeighbor); + } + else +#endif + if ((neighbor != nullptr) && !neighbor->IsRxOnWhenIdle() && !message.IsDirectTransmission() && + Get().Contains(*neighbor)) { mIndirectSender.AddMessageForSleepyChild(message, *static_cast(neighbor)); } diff --git a/src/core/thread/mesh_forwarder_mtd.cpp b/src/core/thread/mesh_forwarder_mtd.cpp index 9dec26b43b8..1dc99200750 100644 --- a/src/core/thread/mesh_forwarder_mtd.cpp +++ b/src/core/thread/mesh_forwarder_mtd.cpp @@ -35,16 +35,29 @@ #if OPENTHREAD_MTD +#include "instance/instance.hpp" + namespace ot { void MeshForwarder::SendMessage(OwnedPtr aMessagePtr) { Message &message = *aMessagePtr.Release(); - message.SetDirectTransmission(); - message.SetOffset(0); - message.SetDatagramTag(0); - message.SetTimestampToNow(); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + CslNeighbor *cslNeighbor = Get().GetWakeupParent(); + + if ((cslNeighbor != nullptr) && cslNeighbor->IsCslSynchronized()) + { + mIndirectSender.AddMessageForEnhCslNeighbor(message, *cslNeighbor); + } + else +#endif + { + message.SetDirectTransmission(); + message.SetOffset(0); + message.SetDatagramTag(0); + message.SetTimestampToNow(); + } mSendQueue.Enqueue(message); mScheduleTransmissionTask.Post(); diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 79960795dad..3c007e9aebf 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -91,6 +91,9 @@ Mle::Mle(Instance &aInstance) , mWedAttachState(kWedDetached) , mWedAttachTimer(aInstance) #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + , mCslNeighborTable(aInstance) +#endif { mParent.Init(aInstance); mParentCandidate.Init(aInstance); @@ -5507,6 +5510,52 @@ Error Mle::RxMessage::ReadRouteTlv(RouteTlv &aRouteTlv) const } #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +void Mle::AddWakeupParent(const Mac::ExtAddress &aParent, TimeMilli aAttachTime, uint32_t aAttachWindowMs) +{ + CslNeighbor *parent = nullptr; + + parent = mCslNeighborTable.GetNewCslNeighbor(); + parent->SetExtAddress(aParent); + + mWakeupParentAttachTime = aAttachTime; + mWakeupParentAttachWindow = aAttachWindowMs; +} + +CslNeighbor *Mle::GetWakeupParent(void) { return mCslNeighborTable.GetFirstCslNeighbor(); } + +void Mle::AttachToWakeupParent() +{ + CslNeighbor *parent = GetWakeupParent(); + + OT_ASSERT(parent != nullptr); + + // TODO: Implement the logic to attach to the wakeup parent. +} +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +CslNeighbor *Mle::FindCslNeighbor(const Mac::Address &aAddress) +{ + CslNeighbor *neighbor = nullptr; + + OT_UNUSED_VARIABLE(aAddress); + +#if OPENTHREAD_FTD + neighbor = Get().FindChild(aAddress, Child::kInStateAnyExceptInvalid); +#endif + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (neighbor == nullptr) + { + neighbor = GetWakeupParent(); + } +#endif + + return neighbor; +} +#endif + //--------------------------------------------------------------------------------------------------------------------- // ParentCandidate diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index a24f71e0d00..544a576e8b5 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -51,6 +51,7 @@ #include "meshcop/meshcop.hpp" #include "net/udp6.hpp" #include "thread/child.hpp" +#include "thread/enh_csl_neighbor_table.hpp" #include "thread/link_metrics.hpp" #include "thread/link_metrics_tlvs.hpp" #include "thread/mle_tlvs.hpp" @@ -753,6 +754,49 @@ class Mle : public InstanceLocator, private NonCopyable void *aCallbackContext); #endif // OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + /** + * Returns whether the Thread interface is currently communicating to a Wake-up Parent. + * + * @retval TRUE If the Thread interface is communicating to a Wake-up Parent. + * @retval FALSE If the Thread interface is not communicating to a Wake-up Parent. + */ + bool IsWakeupParentPresent(void) const { return mWakeupParentAttachWindow > 0; } + + /** + * Adds a Wake-up Parent to the list of potential parents. + * + * @param[in] aParent The extended address of the Wake-up Parent. + * @param[in] aAttachTime The time when the Thread interface attached to the Wake-up Parent. + * @param[in] aAttachWindowMs The time window in milliseconds during which the Thread interface can attach to the + * Wake-up Parent. + */ + void AddWakeupParent(const Mac::ExtAddress &aParent, TimeMilli aAttachTime, uint32_t aAttachWindowMs); + + /** + * Returns the Wake-up Parent that the Thread interface is currently communicating to. + * + * @returns The Wake-up Parent that the Thread interface is currently communicating to. + */ + CslNeighbor *GetWakeupParent(void); + + /** + * Starts the process of attaching to a Wake-up Parent, if previously configured with `AddWakeupParent`. + */ + void AttachToWakeupParent(); +#endif + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + /** + * Returns a CSL Neighbor by its address. + * + * @param[in] aAddress The address of the CSL Neighbor. + * + * @returns A pointer to the CSL Neighbor, or NULL if not found. + */ + CslNeighbor *FindCslNeighbor(const Mac::Address &aAddress); +#endif + private: //------------------------------------------------------------------------------------------------------------------ // Constants @@ -1519,6 +1563,11 @@ class Mle : public InstanceLocator, private NonCopyable WedAttachTimer mWedAttachTimer; Callback mWakeupCallback; #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + TimeMilli mWakeupParentAttachTime; + uint32_t mWakeupParentAttachWindow; + CslNeighborTable mCslNeighborTable; +#endif }; } // namespace Mle diff --git a/src/core/thread/neighbor.hpp b/src/core/thread/neighbor.hpp index b7e90e647f1..3a5820d049f 100644 --- a/src/core/thread/neighbor.hpp +++ b/src/core/thread/neighbor.hpp @@ -783,6 +783,9 @@ class CslNeighbor : public Neighbor public CslTxScheduler::NeighborInfo #endif { +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + friend class CslNeighborTable; +#endif }; } // namespace ot