blob: 6b4e68c8ef8fe21e4d4b218f55549ff771f42241 [file] [log] [blame]
/*
* Copyright (c) 2019, 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 handling indirect transmission.
*/
#include "indirect_sender.hpp"
#if OPENTHREAD_FTD
#include "common/code_utils.hpp"
#include "common/instance.hpp"
#include "common/locator_getters.hpp"
#include "common/message.hpp"
#include "thread/mesh_forwarder.hpp"
#include "thread/mle_tlvs.hpp"
#include "thread/topology.hpp"
namespace ot {
const Mac::Address &IndirectSender::ChildInfo::GetMacAddress(Mac::Address &aMacAddress) const
{
if (mUseShortAddress)
{
aMacAddress.SetShort(static_cast<const Child *>(this)->GetRloc16());
}
else
{
aMacAddress.SetExtended(static_cast<const Child *>(this)->GetExtAddress());
}
return aMacAddress;
}
IndirectSender::IndirectSender(Instance &aInstance)
: InstanceLocator(aInstance)
, mEnabled(false)
, mSourceMatchController(aInstance)
, mDataPollHandler(aInstance)
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
, mCslTxScheduler(aInstance)
#endif
{
}
void IndirectSender::Stop(void)
{
VerifyOrExit(mEnabled);
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
{
child.SetIndirectMessage(nullptr);
mSourceMatchController.ResetMessageCount(child);
}
mDataPollHandler.Clear();
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
mCslTxScheduler.Clear();
#endif
exit:
mEnabled = false;
}
void IndirectSender::AddMessageForSleepyChild(Message &aMessage, Child &aChild)
{
uint16_t childIndex;
OT_ASSERT(!aChild.IsRxOnWhenIdle());
childIndex = Get<ChildTable>().GetChildIndex(aChild);
VerifyOrExit(!aMessage.GetChildMask(childIndex));
aMessage.SetChildMask(childIndex);
mSourceMatchController.IncrementMessageCount(aChild);
if ((aMessage.GetType() != Message::kTypeSupervision) && (aChild.GetIndirectMessageCount() > 1))
{
Message *supervisionMessage = FindIndirectMessage(aChild, /* aSupervisionTypeOnly */ true);
if (supervisionMessage != nullptr)
{
IgnoreError(RemoveMessageFromSleepyChild(*supervisionMessage, aChild));
Get<MeshForwarder>().RemoveMessageIfNoPendingTx(*supervisionMessage);
}
}
RequestMessageUpdate(aChild);
exit:
return;
}
Error IndirectSender::RemoveMessageFromSleepyChild(Message &aMessage, Child &aChild)
{
Error error = kErrorNone;
uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
VerifyOrExit(aMessage.GetChildMask(childIndex), error = kErrorNotFound);
aMessage.ClearChildMask(childIndex);
mSourceMatchController.DecrementMessageCount(aChild);
RequestMessageUpdate(aChild);
exit:
return error;
}
void IndirectSender::ClearAllMessagesForSleepyChild(Child &aChild)
{
VerifyOrExit(aChild.GetIndirectMessageCount() > 0);
for (Message &message : Get<MeshForwarder>().mSendQueue)
{
message.ClearChildMask(Get<ChildTable>().GetChildIndex(aChild));
Get<MeshForwarder>().RemoveMessageIfNoPendingTx(message);
}
aChild.SetIndirectMessage(nullptr);
mSourceMatchController.ResetMessageCount(aChild);
mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
mCslTxScheduler.Update();
#endif
exit:
return;
}
void IndirectSender::SetChildUseShortAddress(Child &aChild, bool aUseShortAddress)
{
VerifyOrExit(aChild.IsIndirectSourceMatchShort() != aUseShortAddress);
mSourceMatchController.SetSrcMatchAsShort(aChild, aUseShortAddress);
exit:
return;
}
void IndirectSender::HandleChildModeChange(Child &aChild, Mle::DeviceMode aOldMode)
{
if (!aChild.IsRxOnWhenIdle() && (aChild.IsStateValid()))
{
SetChildUseShortAddress(aChild, true);
}
// On sleepy to non-sleepy mode change, convert indirect messages in
// the send queue destined to the child to direct.
if (!aOldMode.IsRxOnWhenIdle() && aChild.IsRxOnWhenIdle() && (aChild.GetIndirectMessageCount() > 0))
{
uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
for (Message &message : Get<MeshForwarder>().mSendQueue)
{
if (message.GetChildMask(childIndex))
{
message.ClearChildMask(childIndex);
message.SetDirectTransmission();
}
}
aChild.SetIndirectMessage(nullptr);
mSourceMatchController.ResetMessageCount(aChild);
mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
mCslTxScheduler.Update();
#endif
}
// Since the queuing delays for direct transmissions are expected to
// be relatively small especially when compared to indirect, for a
// non-sleepy to sleepy mode change, we allow any direct message
// (for the child) already in the send queue to remain as is. This
// is equivalent to dropping the already queued messages in this
// case.
}
Message *IndirectSender::FindIndirectMessage(Child &aChild, bool aSupervisionTypeOnly)
{
Message *msg = nullptr;
uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
for (Message &message : Get<MeshForwarder>().mSendQueue)
{
if (message.GetChildMask(childIndex) &&
(!aSupervisionTypeOnly || (message.GetType() == Message::kTypeSupervision)))
{
msg = &message;
break;
}
}
return msg;
}
void IndirectSender::RequestMessageUpdate(Child &aChild)
{
Message *curMessage = aChild.GetIndirectMessage();
Message *newMessage;
// Purge the frame if the current message is no longer destined
// for the child. This check needs to be done first to cover the
// case where we have a pending "replace frame" request and while
// waiting for the callback, the current message is removed.
if ((curMessage != nullptr) && !curMessage->GetChildMask(Get<ChildTable>().GetChildIndex(aChild)))
{
// Set the indirect message for this child to `nullptr` to ensure
// it is not processed on `HandleSentFrameToChild()` callback.
aChild.SetIndirectMessage(nullptr);
// Request a "frame purge" using `RequestFrameChange()` and
// wait for `HandleFrameChangeDone()` callback for completion
// of the request. Note that the callback may be directly
// called from the `RequestFrameChange()` itself when the
// request can be handled immediately.
aChild.SetWaitingForMessageUpdate(true);
mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
mCslTxScheduler.Update();
#endif
ExitNow();
}
VerifyOrExit(!aChild.IsWaitingForMessageUpdate());
newMessage = FindIndirectMessage(aChild);
VerifyOrExit(curMessage != newMessage);
if (curMessage == nullptr)
{
// Current message is `nullptr`, but new message is not.
// We have a new indirect message.
UpdateIndirectMessage(aChild);
ExitNow();
}
// Current message and new message differ and are both
// non-`nullptr`. We need to request the frame to be replaced.
// The current indirect message can be replaced only if it is
// the first fragment. If a next fragment frame for message is
// already prepared, we wait for the entire message to be
// delivered.
VerifyOrExit(aChild.GetIndirectFragmentOffset() == 0);
aChild.SetWaitingForMessageUpdate(true);
mDataPollHandler.RequestFrameChange(DataPollHandler::kReplaceFrame, aChild);
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
mCslTxScheduler.Update();
#endif
exit:
return;
}
void IndirectSender::HandleFrameChangeDone(Child &aChild)
{
VerifyOrExit(aChild.IsWaitingForMessageUpdate());
UpdateIndirectMessage(aChild);
exit:
return;
}
void IndirectSender::UpdateIndirectMessage(Child &aChild)
{
Message *message = FindIndirectMessage(aChild);
aChild.SetWaitingForMessageUpdate(false);
aChild.SetIndirectMessage(message);
aChild.SetIndirectFragmentOffset(0);
aChild.SetIndirectTxSuccess(true);
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
mCslTxScheduler.Update();
#endif
if (message != nullptr)
{
Mac::Address childAddress;
mDataPollHandler.HandleNewFrame(aChild);
aChild.GetMacAddress(childAddress);
Get<MeshForwarder>().LogMessage(MeshForwarder::kMessagePrepareIndirect, *message, kErrorNone, &childAddress);
}
}
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, kSupervisionMsgAckRequest);
aContext.mMessageNextOffset = message->GetLength();
break;
default:
OT_ASSERT(false);
OT_UNREACHABLE_CODE(break);
}
exit:
return error;
}
uint16_t IndirectSender::PrepareDataFrame(Mac::TxFrame &aFrame, Child &aChild, Message &aMessage)
{
Ip6::Header ip6Header;
Mac::Address macSource, macDest;
uint16_t directTxOffset;
uint16_t nextOffset;
// Determine the MAC source and destination addresses.
IgnoreError(aMessage.Read(0, ip6Header));
Get<MeshForwarder>().GetMacSourceAddress(ip6Header.GetSource(), macSource);
if (ip6Header.GetDestination().IsLinkLocal())
{
Get<MeshForwarder>().GetMacDestinationAddress(ip6Header.GetDestination(), macDest);
}
else
{
aChild.GetMacAddress(macDest);
}
// Prepare the data frame from previous child's indirect offset.
directTxOffset = aMessage.GetOffset();
aMessage.SetOffset(aChild.GetIndirectFragmentOffset());
nextOffset = Get<MeshForwarder>().PrepareDataFrame(aFrame, aMessage, macSource, macDest);
aMessage.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.
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);
}
void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame,
const FrameContext &aContext,
Error aError,
Child & aChild)
{
Message *message = aChild.GetIndirectMessage();
uint16_t nextOffset = aContext.mMessageNextOffset;
VerifyOrExit(mEnabled);
#if OPENTHREAD_CONFIG_CHILD_SUPERVISION_ENABLE
if (aError == kErrorNone)
{
Get<Utils::ChildSupervisor>().UpdateOnSend(aChild);
}
#endif
// A zero `nextOffset` indicates that the sent frame is an empty
// frame generated by `PrepareFrameForChild()` when there was no
// indirect message in the send queue for the child. This can happen
// in the (not common) case where the radio platform does not
// 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
// 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);
switch (aError)
{
case kErrorNone:
break;
case kErrorNoAck:
case kErrorChannelAccessFailure:
case kErrorAbort:
aChild.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
// attempts of current frame already failed.
if (message != nullptr)
{
nextOffset = message->GetLength();
}
#endif
break;
default:
OT_ASSERT(false);
OT_UNREACHABLE_CODE(break);
}
if ((message != nullptr) && (nextOffset < message->GetLength()))
{
aChild.SetIndirectFragmentOffset(nextOffset);
mDataPollHandler.HandleNewFrame(aChild);
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
mCslTxScheduler.Update();
#endif
ExitNow();
}
if (message != nullptr)
{
// The indirect tx of this message to the child is done.
Error txError = aError;
uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
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.
mSourceMatchController.SetSrcMatchAsShort(aChild, true);
#if !OPENTHREAD_CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE
// When `CONFIG_DROP_MESSAGE_ON_FRAGMENT_TX_FAILURE` is
// 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`
// represents the error status of the last fragment frame
// transmission.
if (!aChild.GetIndirectTxSuccess() && (txError == kErrorNone))
{
txError = kErrorFailed;
}
#endif
if (!aFrame.IsEmpty())
{
IgnoreError(aFrame.GetDstAddr(macDest));
Get<MeshForwarder>().LogMessage(MeshForwarder::kMessageTransmit, *message, txError, &macDest);
}
if (message->GetType() == Message::kTypeIp6)
{
if (aChild.GetIndirectTxSuccess())
{
Get<MeshForwarder>().mIpCounters.mTxSuccess++;
}
else
{
Get<MeshForwarder>().mIpCounters.mTxFailure++;
}
}
if (message->GetChildMask(childIndex))
{
message->ClearChildMask(childIndex);
mSourceMatchController.DecrementMessageCount(aChild);
}
Get<MeshForwarder>().RemoveMessageIfNoPendingTx(*message);
}
UpdateIndirectMessage(aChild);
exit:
if (mEnabled)
{
ClearMessagesForRemovedChildren();
}
}
void IndirectSender::ClearMessagesForRemovedChildren(void)
{
for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptValidOrRestoring))
{
if (child.GetIndirectMessageCount() == 0)
{
continue;
}
ClearAllMessagesForSleepyChild(child);
}
}
} // namespace ot
#endif // #if OPENTHREAD_FTD