| /* |
| * Copyright (c) 2017, 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 implements data poll (mac data request command) sender class. |
| */ |
| |
| #include "data_poll_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 "net/ip6.hpp" |
| #include "net/netif.hpp" |
| #include "thread/mesh_forwarder.hpp" |
| #include "thread/mle.hpp" |
| #include "thread/thread_netif.hpp" |
| |
| namespace ot { |
| |
| DataPollSender::DataPollSender(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mTimerStartTime(0) |
| , mPollPeriod(0) |
| , mExternalPollPeriod(0) |
| , mFastPollsUsers(0) |
| , mTimer(aInstance, DataPollSender::HandlePollTimer, this) |
| , mEnabled(false) |
| , mAttachMode(false) |
| , mRetxMode(false) |
| , mPollTimeoutCounter(0) |
| , mPollTxFailureCounter(0) |
| , mRemainingFastPolls(0) |
| { |
| } |
| |
| const Neighbor &DataPollSender::GetParent(void) const |
| { |
| const Neighbor &parentCandidate = Get<Mle::MleRouter>().GetParentCandidate(); |
| |
| return parentCandidate.IsStateValid() ? parentCandidate : Get<Mle::MleRouter>().GetParent(); |
| } |
| |
| void DataPollSender::StartPolling(void) |
| { |
| VerifyOrExit(!mEnabled, OT_NOOP); |
| |
| OT_ASSERT(!Get<Mle::MleRouter>().IsRxOnWhenIdle()); |
| |
| mEnabled = true; |
| ScheduleNextPoll(kRecalculatePollPeriod); |
| |
| exit: |
| return; |
| } |
| |
| void DataPollSender::StopPolling(void) |
| { |
| mTimer.Stop(); |
| mAttachMode = false; |
| mRetxMode = false; |
| mPollTimeoutCounter = 0; |
| mPollTxFailureCounter = 0; |
| mRemainingFastPolls = 0; |
| mFastPollsUsers = 0; |
| mEnabled = false; |
| } |
| |
| otError DataPollSender::SendDataPoll(void) |
| { |
| otError error; |
| |
| VerifyOrExit(mEnabled, error = OT_ERROR_INVALID_STATE); |
| VerifyOrExit(!Get<Mac::Mac>().GetRxOnWhenIdle(), error = OT_ERROR_INVALID_STATE); |
| |
| VerifyOrExit(GetParent().IsStateValidOrRestoring(), error = OT_ERROR_INVALID_STATE); |
| |
| mTimer.Stop(); |
| |
| SuccessOrExit(error = Get<Mac::Mac>().RequestDataPollTransmission()); |
| |
| exit: |
| |
| switch (error) |
| { |
| case OT_ERROR_NONE: |
| otLogDebgMac("Sending data poll"); |
| ScheduleNextPoll(kUsePreviousPollPeriod); |
| break; |
| |
| case OT_ERROR_INVALID_STATE: |
| otLogWarnMac("Data poll tx requested while data polling was not enabled!"); |
| StopPolling(); |
| break; |
| |
| case OT_ERROR_ALREADY: |
| otLogDebgMac("Data poll tx requested when a previous data request still in send queue."); |
| ScheduleNextPoll(kUsePreviousPollPeriod); |
| break; |
| |
| default: |
| otLogWarnMac("Unexpected error %s requesting data poll", otThreadErrorToString(error)); |
| ScheduleNextPoll(kRecalculatePollPeriod); |
| break; |
| } |
| |
| return error; |
| } |
| |
| otError DataPollSender::GetPollDestinationAddress(Mac::Address &aDest) const |
| { |
| otError error = OT_ERROR_NONE; |
| const Neighbor &parent = GetParent(); |
| |
| VerifyOrExit(parent.IsStateValidOrRestoring(), error = OT_ERROR_ABORT); |
| |
| // Use extended address attaching to a new parent (i.e. parent is the parent candidate). |
| if ((Get<Mac::Mac>().GetShortAddress() == Mac::kShortAddrInvalid) || |
| (&parent == &Get<Mle::MleRouter>().GetParentCandidate())) |
| { |
| aDest.SetExtended(parent.GetExtAddress()); |
| } |
| else |
| { |
| aDest.SetShort(parent.GetRloc16()); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| otError DataPollSender::SetExternalPollPeriod(uint32_t aPeriod) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| if (aPeriod != 0) |
| { |
| VerifyOrExit(aPeriod >= OPENTHREAD_CONFIG_MAC_MINIMUM_POLL_PERIOD, error = OT_ERROR_INVALID_ARGS); |
| |
| // Clipped by the maximal value. |
| if (aPeriod > kMaxExternalPeriod) |
| { |
| aPeriod = kMaxExternalPeriod; |
| } |
| } |
| |
| if (mExternalPollPeriod != aPeriod) |
| { |
| mExternalPollPeriod = aPeriod; |
| |
| if (mEnabled) |
| { |
| ScheduleNextPoll(kRecalculatePollPeriod); |
| } |
| } |
| |
| exit: |
| return error; |
| } |
| |
| uint32_t DataPollSender::GetKeepAlivePollPeriod(void) const |
| { |
| uint32_t period = GetDefaultPollPeriod(); |
| |
| if (mExternalPollPeriod != 0) |
| { |
| UpdateIfLarger(period, mExternalPollPeriod); |
| } |
| |
| return period; |
| } |
| |
| void DataPollSender::HandlePollSent(Mac::TxFrame &aFrame, otError aError) |
| { |
| Mac::Address macDest; |
| bool shouldRecalculatePollPeriod = false; |
| |
| VerifyOrExit(mEnabled, OT_NOOP); |
| |
| if (!aFrame.IsEmpty()) |
| { |
| IgnoreError(aFrame.GetDstAddr(macDest)); |
| Get<MeshForwarder>().UpdateNeighborOnSentFrame(aFrame, aError, macDest); |
| } |
| |
| if (GetParent().IsStateInvalid()) |
| { |
| StopPolling(); |
| IgnoreError(Get<Mle::MleRouter>().BecomeDetached()); |
| ExitNow(); |
| } |
| |
| switch (aError) |
| { |
| case OT_ERROR_NONE: |
| |
| if (mRemainingFastPolls != 0) |
| { |
| mRemainingFastPolls--; |
| |
| if (mRemainingFastPolls == 0) |
| { |
| shouldRecalculatePollPeriod = true; |
| mFastPollsUsers = 0; |
| } |
| } |
| |
| if (mRetxMode) |
| { |
| mRetxMode = false; |
| mPollTxFailureCounter = 0; |
| shouldRecalculatePollPeriod = true; |
| } |
| |
| break; |
| |
| case OT_ERROR_CHANNEL_ACCESS_FAILURE: |
| case OT_ERROR_ABORT: |
| mRetxMode = true; |
| shouldRecalculatePollPeriod = true; |
| break; |
| |
| default: |
| mPollTxFailureCounter++; |
| |
| otLogInfoMac("Failed to send data poll, error:%s, retx:%d/%d", otThreadErrorToString(aError), |
| mPollTxFailureCounter, kMaxPollRetxAttempts); |
| |
| if (mPollTxFailureCounter < kMaxPollRetxAttempts) |
| { |
| if (!mRetxMode) |
| { |
| mRetxMode = true; |
| shouldRecalculatePollPeriod = true; |
| } |
| } |
| else |
| { |
| mRetxMode = false; |
| mPollTxFailureCounter = 0; |
| shouldRecalculatePollPeriod = true; |
| } |
| |
| break; |
| } |
| |
| if (shouldRecalculatePollPeriod) |
| { |
| ScheduleNextPoll(kRecalculatePollPeriod); |
| } |
| |
| exit: |
| return; |
| } |
| |
| void DataPollSender::HandlePollTimeout(void) |
| { |
| // A data poll timeout happened, i.e., the ack in response to |
| // a data poll indicated that a frame was pending, but no frame |
| // was received after timeout interval. |
| |
| VerifyOrExit(mEnabled, OT_NOOP); |
| |
| mPollTimeoutCounter++; |
| |
| otLogInfoMac("Data poll timeout, retry:%d/%d", mPollTimeoutCounter, kQuickPollsAfterTimeout); |
| |
| if (mPollTimeoutCounter < kQuickPollsAfterTimeout) |
| { |
| IgnoreError(SendDataPoll()); |
| } |
| else |
| { |
| mPollTimeoutCounter = 0; |
| } |
| |
| exit: |
| return; |
| } |
| |
| void DataPollSender::ProcessFrame(const Mac::RxFrame &aFrame) |
| { |
| VerifyOrExit(mEnabled, OT_NOOP); |
| |
| mPollTimeoutCounter = 0; |
| |
| if (aFrame.GetFramePending()) |
| { |
| IgnoreError(SendDataPoll()); |
| } |
| #if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 |
| else if (aFrame.IsAck()) |
| { |
| ResetKeepAliveTimer(); |
| } |
| #endif |
| |
| exit: |
| return; |
| } |
| |
| void DataPollSender::RecalculatePollPeriod(void) |
| { |
| if (mEnabled) |
| { |
| ScheduleNextPoll(kRecalculatePollPeriod); |
| } |
| } |
| |
| void DataPollSender::SetAttachMode(bool aMode) |
| { |
| if (mAttachMode != aMode) |
| { |
| mAttachMode = aMode; |
| |
| if (mEnabled) |
| { |
| ScheduleNextPoll(kRecalculatePollPeriod); |
| } |
| } |
| } |
| |
| void DataPollSender::SendFastPolls(uint8_t aNumFastPolls) |
| { |
| bool shouldRecalculatePollPeriod = (mRemainingFastPolls == 0); |
| |
| if (mFastPollsUsers < kMaxFastPollsUsers) |
| { |
| mFastPollsUsers++; |
| } |
| |
| if (aNumFastPolls == 0) |
| { |
| aNumFastPolls = kDefaultFastPolls; |
| } |
| |
| if (aNumFastPolls > kMaxFastPolls) |
| { |
| aNumFastPolls = kMaxFastPolls; |
| } |
| |
| if (mRemainingFastPolls < aNumFastPolls) |
| { |
| mRemainingFastPolls = aNumFastPolls; |
| } |
| |
| if (mEnabled && shouldRecalculatePollPeriod) |
| { |
| ScheduleNextPoll(kRecalculatePollPeriod); |
| } |
| } |
| |
| void DataPollSender::StopFastPolls(void) |
| { |
| VerifyOrExit(mFastPollsUsers != 0, OT_NOOP); |
| |
| // If `mFastPollsUsers` hits the max, let it be cleared |
| // from `HandlePollSent()` (after all fast polls are sent). |
| VerifyOrExit(mFastPollsUsers < kMaxFastPollsUsers, OT_NOOP); |
| |
| mFastPollsUsers--; |
| |
| VerifyOrExit(mFastPollsUsers == 0, OT_NOOP); |
| |
| mRemainingFastPolls = 0; |
| ScheduleNextPoll(kRecalculatePollPeriod); |
| |
| exit: |
| return; |
| } |
| |
| void DataPollSender::ResetKeepAliveTimer(void) |
| { |
| if (mTimer.IsRunning() && mPollPeriod == GetDefaultPollPeriod()) |
| { |
| mTimerStartTime = TimerMilli::GetNow(); |
| mTimer.StartAt(mTimerStartTime, mPollPeriod); |
| } |
| } |
| |
| void DataPollSender::ScheduleNextPoll(PollPeriodSelector aPollPeriodSelector) |
| { |
| TimeMilli now; |
| uint32_t oldPeriod = mPollPeriod; |
| |
| if (aPollPeriodSelector == kRecalculatePollPeriod) |
| { |
| mPollPeriod = CalculatePollPeriod(); |
| } |
| |
| now = TimerMilli::GetNow(); |
| |
| if (mTimer.IsRunning()) |
| { |
| if (oldPeriod != mPollPeriod) |
| { |
| // If poll interval did change and re-starting the timer from |
| // last start time with new poll interval would fire quickly |
| // (i.e., fires within window `[now, now + kMinPollPeriod]`) |
| // add an extra minimum delay of `kMinPollPeriod`. This |
| // ensures that when an internal or external request triggers |
| // a switch to a shorter poll interval, the first data poll |
| // will not be sent too quickly (and possibly before the |
| // response is available/prepared on the parent node). |
| |
| if (mTimerStartTime + mPollPeriod < now + kMinPollPeriod) |
| { |
| mTimer.StartAt(now, kMinPollPeriod); |
| } |
| else |
| { |
| mTimer.StartAt(mTimerStartTime, mPollPeriod); |
| } |
| } |
| // Do nothing on the running poll timer if the poll interval doesn't change |
| } |
| else |
| { |
| mTimerStartTime = now; |
| mTimer.StartAt(mTimerStartTime, mPollPeriod); |
| } |
| } |
| |
| void DataPollSender::UpdateIfLarger(uint32_t &aPeriod, uint32_t aNewPeriod) |
| { |
| if (aPeriod > aNewPeriod) |
| { |
| aPeriod = aNewPeriod; |
| } |
| } |
| |
| uint32_t DataPollSender::CalculatePollPeriod(void) const |
| { |
| uint32_t period = GetDefaultPollPeriod(); |
| |
| if (mAttachMode) |
| { |
| UpdateIfLarger(period, kAttachDataPollPeriod); |
| } |
| |
| if (mRetxMode) |
| { |
| UpdateIfLarger(period, kRetxPollPeriod); |
| } |
| |
| if (mRemainingFastPolls != 0) |
| { |
| UpdateIfLarger(period, kFastPollPeriod); |
| } |
| |
| if (mExternalPollPeriod != 0) |
| { |
| UpdateIfLarger(period, mExternalPollPeriod); |
| } |
| |
| if (period == 0) |
| { |
| period = kMinPollPeriod; |
| } |
| |
| return period; |
| } |
| |
| void DataPollSender::HandlePollTimer(Timer &aTimer) |
| { |
| IgnoreError(aTimer.GetOwner<DataPollSender>().SendDataPoll()); |
| } |
| |
| uint32_t DataPollSender::GetDefaultPollPeriod(void) const |
| { |
| return Time::SecToMsec(Get<Mle::MleRouter>().GetTimeout()) - |
| static_cast<uint32_t>(kRetxPollPeriod) * kMaxPollRetxAttempts; |
| } |
| |
| } // namespace ot |