blob: 38428e92ce347fefc4292df26e27f7833b9edbb6 [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.
*/
#if OPENTHREAD_FTD
#include "indirect_sender.hpp"
#include "common/code_utils.hpp"
#include "common/instance.hpp"
#include "common/locator-getters.hpp"
#include "common/logging.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)
{
}
void IndirectSender::Stop(void)
{
VerifyOrExit(mEnabled);
for (ChildTable::Iterator iter(GetInstance(), Child::kInStateAnyExceptInvalid); !iter.IsDone(); iter++)
{
iter.GetChild()->SetIndirectMessage(NULL);
mSourceMatchController.ResetMessageCount(*iter.GetChild());
}
mDataPollHandler.Clear();
exit:
mEnabled = false;
}
otError IndirectSender::AddMessageForSleepyChild(Message &aMessage, Child &aChild)
{
otError error = OT_ERROR_NONE;
uint16_t childIndex;
VerifyOrExit(!aChild.IsRxOnWhenIdle(), error = OT_ERROR_INVALID_STATE);
childIndex = Get<ChildTable>().GetChildIndex(aChild);
VerifyOrExit(!aMessage.GetChildMask(childIndex), error = OT_ERROR_ALREADY);
aMessage.SetChildMask(childIndex);
mSourceMatchController.IncrementMessageCount(aChild);
RequestMessageUpdate(aChild);
exit:
return error;
}
otError IndirectSender::RemoveMessageFromSleepyChild(Message &aMessage, Child &aChild)
{
otError error = OT_ERROR_NONE;
uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
VerifyOrExit(aMessage.GetChildMask(childIndex), error = OT_ERROR_NOT_FOUND);
aMessage.ClearChildMask(childIndex);
mSourceMatchController.DecrementMessageCount(aChild);
RequestMessageUpdate(aChild);
exit:
return error;
}
void IndirectSender::ClearAllMessagesForSleepyChild(Child &aChild)
{
Message *message;
Message *nextMessage;
VerifyOrExit(aChild.GetIndirectMessageCount() > 0);
for (message = Get<MeshForwarder>().mSendQueue.GetHead(); message; message = nextMessage)
{
nextMessage = message->GetNext();
message->ClearChildMask(Get<ChildTable>().GetChildIndex(aChild));
if (!message->IsChildPending() && !message->GetDirectTransmission())
{
if (Get<MeshForwarder>().mSendMessage == message)
{
Get<MeshForwarder>().mSendMessage = NULL;
}
Get<MeshForwarder>().mSendQueue.Dequeue(*message);
message->Free();
}
}
aChild.SetIndirectMessage(NULL);
mSourceMatchController.ResetMessageCount(aChild);
mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
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.GetHead(); message; message = message->GetNext())
{
if (message->GetChildMask(childIndex))
{
message->ClearChildMask(childIndex);
message->SetDirectTransmission();
}
}
aChild.SetIndirectMessage(NULL);
mSourceMatchController.ResetMessageCount(aChild);
mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild);
}
// 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)
{
Message *message;
Message *next;
uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
for (message = Get<MeshForwarder>().mSendQueue.GetHead(); message; message = next)
{
next = message->GetNext();
if (message->GetChildMask(childIndex))
{
// Skip and remove the supervision message if there are
// other messages queued for the child.
if ((message->GetType() == Message::kTypeSupervision) && (aChild.GetIndirectMessageCount() > 1))
{
message->ClearChildMask(childIndex);
mSourceMatchController.DecrementMessageCount(aChild);
Get<MeshForwarder>().mSendQueue.Dequeue(*message);
message->Free();
continue;
}
break;
}
}
return message;
}
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 != NULL) && !curMessage->GetChildMask(Get<ChildTable>().GetChildIndex(aChild)))
{
// Set the indirect message for this child to NULL to ensure
// it is not processed on `HandleSentFrameToChild()` callback.
aChild.SetIndirectMessage(NULL);
// 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);
ExitNow();
}
VerifyOrExit(!aChild.IsWaitingForMessageUpdate());
newMessage = FindIndirectMessage(aChild);
VerifyOrExit(curMessage != newMessage);
if (curMessage == NULL)
{
// Current message is NULL, but new message is not.
// We have a new indirect message.
UpdateIndirectMessage(aChild);
ExitNow();
}
// Current message and new message differ and are both non-NULL.
// 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);
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 (message != NULL)
{
Mac::Address childAddress;
mDataPollHandler.HandleNewFrame(aChild);
aChild.GetMacAddress(childAddress);
Get<MeshForwarder>().LogMessage(MeshForwarder::kMessagePrepareIndirect, *message, &childAddress, OT_ERROR_NONE);
}
}
otError IndirectSender::PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild)
{
otError error = OT_ERROR_NONE;
Message *message = aChild.GetIndirectMessage();
VerifyOrExit(mEnabled, error = OT_ERROR_ABORT);
if (message == NULL)
{
PrepareEmptyFrame(aFrame, aChild, /* aAckRequest */ true);
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:
assert(false);
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.
aMessage.Read(0, sizeof(ip6Header), &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)
{
uint16_t fcf;
Mac::Address macSource, macDest;
aChild.GetMacAddress(macDest);
macSource.SetShort(Get<Mac::Mac>().GetShortAddress());
if (macSource.IsShortAddrInvalid() || macDest.IsExtended())
{
macSource.SetExtended(Get<Mac::Mac>().GetExtAddress());
}
fcf = Mac::Frame::kFcfFrameData | Mac::Frame::kFcfFrameVersion2006 | Mac::Frame::kFcfPanidCompression |
Mac::Frame::kFcfSecurityEnabled;
if (aAckRequest)
{
fcf |= Mac::Frame::kFcfAckRequest;
}
fcf |= (macDest.IsShort()) ? Mac::Frame::kFcfDstAddrShort : Mac::Frame::kFcfDstAddrExt;
fcf |= (macSource.IsShort()) ? Mac::Frame::kFcfSrcAddrShort : Mac::Frame::kFcfSrcAddrExt;
aFrame.InitMacHeader(fcf, Mac::Frame::kKeyIdMode1 | Mac::Frame::kSecEncMic32);
aFrame.SetDstPanId(Get<Mac::Mac>().GetPanId());
aFrame.SetSrcPanId(Get<Mac::Mac>().GetPanId());
aFrame.SetDstAddr(macDest);
aFrame.SetSrcAddr(macSource);
aFrame.SetPayloadLength(0);
aFrame.SetFramePending(false);
}
void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame,
const FrameContext &aContext,
otError aError,
Child & aChild)
{
Message *message = aChild.GetIndirectMessage();
uint16_t nextOffset = aContext.mMessageNextOffset;
VerifyOrExit(mEnabled);
switch (aError)
{
case OT_ERROR_NONE:
Get<Utils::ChildSupervisor>().UpdateOnSend(aChild);
break;
case OT_ERROR_NO_ACK:
case OT_ERROR_CHANNEL_ACCESS_FAILURE:
case OT_ERROR_ABORT:
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 != NULL)
{
nextOffset = message->GetLength();
}
#endif
break;
default:
assert(false);
break;
}
if ((message != NULL) && (nextOffset < message->GetLength()))
{
aChild.SetIndirectFragmentOffset(nextOffset);
mDataPollHandler.HandleNewFrame(aChild);
ExitNow();
}
if (message != NULL)
{
// The indirect tx of this message to the child is done.
otError txError = aError;
uint16_t childIndex = Get<ChildTable>().GetChildIndex(aChild);
Mac::Address macDest;
aChild.SetIndirectMessage(NULL);
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 == OT_ERROR_NONE))
{
txError = OT_ERROR_FAILED;
}
#endif
if (!aFrame.IsEmpty())
{
aFrame.GetDstAddr(macDest);
Get<MeshForwarder>().LogMessage(MeshForwarder::kMessageTransmit, *message, &macDest, txError);
}
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);
}
if (!message->GetDirectTransmission() && !message->IsChildPending())
{
Get<MeshForwarder>().mSendQueue.Dequeue(*message);
message->Free();
}
}
UpdateIndirectMessage(aChild);
exit:
if (mEnabled)
{
ClearMessagesForRemovedChildren();
}
}
void IndirectSender::ClearMessagesForRemovedChildren(void)
{
for (ChildTable::Iterator iter(GetInstance(), Child::kInStateAnyExceptValidOrRestoring); !iter.IsDone(); iter++)
{
if (iter.GetChild()->GetIndirectMessageCount() == 0)
{
continue;
}
ClearAllMessagesForSleepyChild(*iter.GetChild());
}
}
} // namespace ot
#endif // #if OPENTHREAD_FTD